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
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,58 @@ describe('delete()', () => {
expect(connectorTokenClient.deleteConnectorTokens).toHaveBeenCalledTimes(1);
});

describe('when connector has authMode per-user', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.get.mockReset();
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
id: '1',
type: 'action',
attributes: {
actionTypeId: 'my-action-delete',
isMissingSecrets: false,
config: {},
secrets: {},
authMode: 'per-user',
},
references: [],
});
});

test(`passes authMode per-user to deleteConnectorTokens`, async () => {
await actionsClient.delete({ id: '1' });
expect(connectorTokenClient.deleteConnectorTokens).toHaveBeenCalledWith({
connectorId: '1',
authMode: 'per-user',
});
});
});

describe('when connector has authMode shared', () => {
beforeEach(() => {
unsecuredSavedObjectsClient.get.mockReset();
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
id: '1',
type: 'action',
attributes: {
actionTypeId: 'my-action-delete',
isMissingSecrets: false,
config: {},
secrets: {},
authMode: 'shared',
},
references: [],
});
});

test(`passes authMode shared to deleteConnectorTokens`, async () => {
await actionsClient.delete({ id: '1' });
expect(connectorTokenClient.deleteConnectorTokens).toHaveBeenCalledWith({
connectorId: '1',
authMode: 'shared',
});
});
});

test(`failing to delete tokens logs error instead of throw`, async () => {
connectorTokenClient.deleteConnectorTokens.mockRejectedValueOnce(new Error('Fail'));
await expect(actionsClient.delete({ id: '1' })).resolves.toBeUndefined();
Expand Down Expand Up @@ -2133,6 +2185,84 @@ describe('update()', () => {
expect(connectorTokenClient.deleteConnectorTokens).toHaveBeenCalledTimes(1);
});

describe('when connector has authMode per-user', () => {
beforeEach(() => {
actionTypeRegistry.register(getConnectorType());
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
id: '1',
type: 'action',
attributes: {
actionTypeId: 'my-connector-type',
isMissingSecrets: false,
authMode: 'per-user',
},
references: [],
});
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: 'my-action',
type: 'action',
attributes: {
actionTypeId: 'my-connector-type',
isMissingSecrets: false,
name: 'my name',
config: {},
secrets: {},
},
references: [],
});
});

test(`passes authMode per-user to deleteConnectorTokens`, async () => {
await actionsClient.update({
id: 'my-action',
action: { name: 'my name', config: {}, secrets: {} },
});
expect(connectorTokenClient.deleteConnectorTokens).toHaveBeenCalledWith({
connectorId: 'my-action',
authMode: 'per-user',
});
});
});

describe('when connector has authMode shared', () => {
beforeEach(() => {
actionTypeRegistry.register(getConnectorType());
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
id: '1',
type: 'action',
attributes: {
actionTypeId: 'my-connector-type',
isMissingSecrets: false,
authMode: 'shared',
},
references: [],
});
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: 'my-action',
type: 'action',
attributes: {
actionTypeId: 'my-connector-type',
isMissingSecrets: false,
name: 'my name',
config: {},
secrets: {},
},
references: [],
});
});

test(`passes authMode shared to deleteConnectorTokens`, async () => {
await actionsClient.update({
id: 'my-action',
action: { name: 'my name', config: {}, secrets: {} },
});
expect(connectorTokenClient.deleteConnectorTokens).toHaveBeenCalledWith({
connectorId: 'my-action',
authMode: 'shared',
});
});
});

test(`failing to delete tokens logs error instead of throw`, async () => {
connectorTokenClient.deleteConnectorTokens.mockRejectedValueOnce(new Error('Fail'));
await expect(updateOperation()).resolves.toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,17 +550,9 @@ export class ActionsClient {
})
);

try {
await this.context.connectorTokenClient.deleteConnectorTokens({ connectorId: id });
} catch (e) {
this.context.logger.error(
`Failed to delete auth tokens for connector "${id}" after delete: ${e.message}`
);
}

const rawAction = await this.context.unsecuredSavedObjectsClient.get<RawAction>('action', id);
const {
attributes: { actionTypeId, config },
attributes: { actionTypeId, config, authMode },
} = rawAction;

let actionType: ActionType | undefined;
Expand Down Expand Up @@ -595,6 +587,18 @@ export class ActionsClient {
);
}
}

try {
await this.context.connectorTokenClient.deleteConnectorTokens({
connectorId: id,
authMode,
});
} catch (e) {
this.context.logger.error(
`Failed to delete auth tokens for connector "${id}" after delete: ${e.message}`
);
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function update({ context, id, action }: ConnectorUpdateParams): Pr
}
const { attributes, references, version } =
await context.unsecuredSavedObjectsClient.get<RawAction>('action', id);
const { actionTypeId } = attributes;
const { actionTypeId, authMode } = attributes;
const { name, config, secrets } = action;
const actionType = context.actionTypeRegistry.get(actionTypeId);
const configurationUtilities = context.actionTypeRegistry.getUtils();
Expand Down Expand Up @@ -163,7 +163,7 @@ export async function update({ context, id, action }: ConnectorUpdateParams): Pr
}

try {
await context.connectorTokenClient.deleteConnectorTokens({ connectorId: id });
await context.connectorTokenClient.deleteConnectorTokens({ connectorId: id, authMode });
} catch (e) {
context.logger.error(
`Failed to delete auth tokens for connector "${id}" after update: ${e.message}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,112 @@ describe('delete()', () => {
]
`);
});

describe('scope routing via authMode and profileUid', () => {
const sharedFindResult = {
total: 1,
per_page: 10,
page: 1,
saved_objects: [
{
id: 'shared-token-1',
type: 'connector_token',
attributes: { connectorId: '123', tokenType: 'access_token' },
score: 1,
references: [],
},
],
};

const userFindResult = {
total: 1,
per_page: 10,
page: 1,
saved_objects: [
{
id: 'user-token-1',
type: 'user_connector_token',
attributes: { connectorId: '123', profileUid: 'user-123', credentialType: 'oauth' },
score: 1,
references: [],
},
],
};

test('routes to shared client when authMode is shared', async () => {
unsecuredSavedObjectsClient.delete.mockResolvedValue({});
unsecuredSavedObjectsClient.find.mockResolvedValueOnce(sharedFindResult);

await connectorTokenClient.deleteConnectorTokens({ connectorId: '123', authMode: 'shared' });

expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith(
'connector_token',
'shared-token-1'
);
});

test('routes to user client when authMode is per-user', async () => {
unsecuredSavedObjectsClient.delete.mockResolvedValue({});
unsecuredSavedObjectsClient.find.mockResolvedValueOnce(userFindResult);

await connectorTokenClient.deleteConnectorTokens({
connectorId: '123',
authMode: 'per-user',
});

expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith(
'user_connector_token',
'user-token-1'
);
});

test('routes to user client when profileUid is provided', async () => {
unsecuredSavedObjectsClient.delete.mockResolvedValue({});
unsecuredSavedObjectsClient.find.mockResolvedValueOnce(userFindResult);

await connectorTokenClient.deleteConnectorTokens({
connectorId: '123',
profileUid: 'user-123',
});

expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith(
'user_connector_token',
'user-token-1'
);
});

test('profileUid takes priority over authMode shared', async () => {
unsecuredSavedObjectsClient.delete.mockResolvedValue({});
unsecuredSavedObjectsClient.find.mockResolvedValueOnce(userFindResult);

await connectorTokenClient.deleteConnectorTokens({
connectorId: '123',
profileUid: 'user-123',
authMode: 'shared',
});

expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith(
'user_connector_token',
'user-token-1'
);
});

test('profileUid takes priority over authMode per-user', async () => {
unsecuredSavedObjectsClient.delete.mockResolvedValue({});
unsecuredSavedObjectsClient.find.mockResolvedValueOnce(userFindResult);

await connectorTokenClient.deleteConnectorTokens({
connectorId: '123',
profileUid: 'user-123',
authMode: 'per-user',
});

expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith(
'user_connector_token',
'user-token-1'
);
});
});
});

describe('updateOrReplace()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,13 @@ export class ConnectorTokenClient {
this.userClient = new UserConnectorTokenClient(options);
}

private getScope(profileUid?: string): typeof PER_USER_TOKEN_SCOPE | typeof SHARED_TOKEN_SCOPE {
return profileUid ? PER_USER_TOKEN_SCOPE : SHARED_TOKEN_SCOPE;
private getScope(
profileUid?: string,
authMode?: typeof PER_USER_TOKEN_SCOPE | typeof SHARED_TOKEN_SCOPE
): typeof PER_USER_TOKEN_SCOPE | typeof SHARED_TOKEN_SCOPE {
return profileUid || (authMode && authMode === PER_USER_TOKEN_SCOPE)
? PER_USER_TOKEN_SCOPE
: SHARED_TOKEN_SCOPE;
}

private parseTokenId(id: string): {
Expand Down Expand Up @@ -193,8 +198,9 @@ export class ConnectorTokenClient {
connectorId: string;
tokenType?: string;
credentialType?: string;
authMode?: typeof PER_USER_TOKEN_SCOPE | typeof SHARED_TOKEN_SCOPE;
}): Promise<void> {
const scope = this.getScope(options.profileUid);
const scope = this.getScope(options.profileUid, options.authMode);
this.log({
method: 'deleteConnectorTokens',
scope,
Expand Down
Loading