-
Notifications
You must be signed in to change notification settings - Fork 7.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ sharing/unsharing a credential (#3601)
* 🎉 initial design * ✨ sharing/unsharing of credentials * ✅ add tests for EE credentials controller * 💪 implement review comments * 🛠 refactor agent creation and credential role locking * 👕 linting adjustments (#3691) * 👕 Adjust rule `naming-convention` * 👕 Fix `naming-convention` config value * 👕 Disregard casing for EE-prefixed vars Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
- Loading branch information
1 parent
f525b9a
commit b9d2301
Showing
22 changed files
with
539 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* eslint-disable import/no-cycle */ | ||
import express from 'express'; | ||
|
||
import config from '../../config'; | ||
import type { CredentialRequest } from '../requests'; | ||
import { UserService } from '../user/user.service'; | ||
import { EECredentialsService as EECredentials } from './credentials.service.ee'; | ||
|
||
export const EECredentialsController = express.Router(); | ||
|
||
EECredentialsController.use((_req, _res, next) => { | ||
if (!config.getEnv('deployment.paid')) { | ||
// skip ee router and use free one | ||
next('router'); | ||
return; | ||
} | ||
// use ee router | ||
next(); | ||
}); | ||
|
||
/** | ||
* (EE) POST /credentials/:id/share | ||
* | ||
* Grant a user access to a credential. | ||
*/ | ||
|
||
EECredentialsController.post('/:id/share', async (req: CredentialRequest.Share, res) => { | ||
const { id } = req.params; | ||
const { shareeId } = req.body; | ||
|
||
const isOwned = EECredentials.isOwned(req.user.id, id); | ||
const getSharee = UserService.get({ id: shareeId }); | ||
|
||
// parallelize DB requests and destructure results | ||
const [{ ownsCredential, credential }, sharee] = await Promise.all([isOwned, getSharee]); | ||
|
||
if (!ownsCredential || !credential) { | ||
return res.status(403).send(); | ||
} | ||
|
||
if (!sharee || sharee.isPending) { | ||
return res.status(400).send('Bad Request'); | ||
} | ||
|
||
await EECredentials.share(credential, sharee); | ||
|
||
return res.status(200).send(); | ||
}); | ||
|
||
/** | ||
* (EE) DELETE /credentials/:id/share | ||
* | ||
* Revoke a user's access to a credential. | ||
*/ | ||
|
||
EECredentialsController.delete('/:id/share', async (req: CredentialRequest.Share, res) => { | ||
const { id } = req.params; | ||
const { shareeId } = req.body; | ||
|
||
const { ownsCredential } = await EECredentials.isOwned(req.user.id, id); | ||
|
||
if (!ownsCredential) { | ||
return res.status(403).send(); | ||
} | ||
|
||
if (!shareeId || typeof shareeId !== 'string') { | ||
return res.status(400).send('Bad Request'); | ||
} | ||
|
||
await EECredentials.unshare(id, shareeId); | ||
|
||
return res.status(200).send(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable import/no-cycle */ | ||
import { Db } from '..'; | ||
import { CredentialsService } from './credentials.service'; | ||
import { CredentialsEntity } from '../databases/entities/CredentialsEntity'; | ||
import { SharedCredentials } from '../databases/entities/SharedCredentials'; | ||
import { User } from '../databases/entities/User'; | ||
import { RoleService } from '../role/role.service'; | ||
|
||
export class EECredentialsService extends CredentialsService { | ||
static async isOwned( | ||
userId: string, | ||
credentialId: string, | ||
): Promise<{ ownsCredential: boolean; credential?: CredentialsEntity }> { | ||
const sharing = await this.getSharing(userId, credentialId, ['credentials']); | ||
|
||
return sharing | ||
? { ownsCredential: true, credential: sharing.credentials } | ||
: { ownsCredential: false }; | ||
} | ||
|
||
static async share(credentials: CredentialsEntity, sharee: User): Promise<SharedCredentials> { | ||
const role = await RoleService.get({ scope: 'credential', name: 'editor' }); | ||
|
||
return Db.collections.SharedCredentials.save({ | ||
credentials, | ||
user: sharee, | ||
role, | ||
}); | ||
} | ||
|
||
static async unshare(credentialId: string, shareeId: string): Promise<void> { | ||
return Db.collections.SharedCredentials.delete({ | ||
credentials: { id: credentialId }, | ||
user: { id: shareeId }, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* eslint-disable import/no-cycle */ | ||
import { Credentials } from 'n8n-core'; | ||
import { FindOneOptions } from 'typeorm'; | ||
|
||
import { Db } from '..'; | ||
import { CredentialsEntity } from '../databases/entities/CredentialsEntity'; | ||
import { SharedCredentials } from '../databases/entities/SharedCredentials'; | ||
|
||
export class CredentialsService { | ||
static async getSharing( | ||
userId: string, | ||
credentialId: number | string, | ||
relations?: string[], | ||
): Promise<SharedCredentials | undefined> { | ||
const options: FindOneOptions = { | ||
where: { | ||
user: { id: userId }, | ||
credentials: { id: credentialId }, | ||
}, | ||
}; | ||
|
||
if (relations) { | ||
options.relations = relations; | ||
} | ||
|
||
return Db.collections.SharedCredentials.findOne(options); | ||
} | ||
|
||
static createCredentialsFromCredentialsEntity( | ||
credential: CredentialsEntity, | ||
encrypt = false, | ||
): Credentials { | ||
const { id, name, type, nodesAccess, data } = credential; | ||
if (encrypt) { | ||
return new Credentials({ id: null, name }, type, nodesAccess); | ||
} | ||
return new Credentials({ id: id.toString(), name }, type, nodesAccess, data); | ||
} | ||
} |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
packages/cli/src/databases/migrations/sqlite/1657062385367-CreateCredentialsEditorRole.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { MigrationInterface, QueryRunner } from 'typeorm'; | ||
import * as config from '../../../../config'; | ||
import { logMigrationEnd, logMigrationStart } from '../../utils/migrationHelpers'; | ||
|
||
export class CreateCredentialsEditorRole1657062385367 implements MigrationInterface { | ||
name = 'CreateCredentialsEditorRole1657062385367'; | ||
|
||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
logMigrationStart(this.name); | ||
|
||
const tablePrefix = config.getEnv('database.tablePrefix'); | ||
|
||
await queryRunner.query(` | ||
INSERT INTO "${tablePrefix}role" (name, scope) | ||
VALUES ("editor", "credential"); | ||
`); | ||
|
||
logMigrationEnd(this.name); | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> { | ||
const tablePrefix = config.getEnv('database.tablePrefix'); | ||
|
||
await queryRunner.query(` | ||
DELETE FROM "${tablePrefix}role" WHERE name='editor' AND scope='credential'; | ||
`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* eslint-disable import/no-cycle */ | ||
import { Db } from '..'; | ||
import { Role } from '../databases/entities/Role'; | ||
|
||
export class RoleService { | ||
static async get(role: Partial<Role>): Promise<Role | undefined> { | ||
return Db.collections.Role.findOne(role); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* eslint-disable import/no-cycle */ | ||
import { Db } from '..'; | ||
import { User } from '../databases/entities/User'; | ||
|
||
export class UserService { | ||
static async get(user: Partial<User>): Promise<User | undefined> { | ||
return Db.collections.User.findOne(user); | ||
} | ||
} |
Oops, something went wrong.