diff --git a/packages/jira-adapter/src/client/connection.ts b/packages/jira-adapter/src/client/connection.ts index e690f2d1e85..a5ac49f56ac 100644 --- a/packages/jira-adapter/src/client/connection.ts +++ b/packages/jira-adapter/src/client/connection.ts @@ -13,11 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { logger } from '@salto-io/logging' import { AccountInfo, CredentialError } from '@salto-io/adapter-api' import { client as clientUtils } from '@salto-io/adapter-components' +import { safeJsonStringify } from '@salto-io/adapter-utils' import { Credentials } from '../auth' import { FORCE_ACCEPT_LANGUAGE_HEADERS } from './headers' +const log = logger(module) + +type appInfo = { + id: string + plan: string +} + const isAuthorized = async ( connection: clientUtils.APIConnection, ): Promise => { @@ -39,12 +48,23 @@ const getBaseUrl = async ( return response.data.baseUrl } +/* +Based on the current implementation of the Jira API, we can't know if the account is a production +account, but in some cases we can know that it's not a production account. +*/ export const validateCredentials = async ( { connection }: { connection: clientUtils.APIConnection }, ): Promise => { if (await isAuthorized(connection)) { const accountId = await getBaseUrl(connection) - return { accountId } + if (accountId.includes('-sandbox-')) { + return { accountId, isProduction: false, accountType: 'Sandbox' } + } + const response = await connection.get('/rest/api/3/instance/license') + log.info(`Jira application's info: ${safeJsonStringify(response.data.applications)}`) + const hasPaidApp = response.data.applications.some((app: appInfo) => app.plan === 'PAID') + const isProduction = hasPaidApp ? undefined : false + return { accountId, isProduction } } throw new CredentialError('Invalid Credentials') } diff --git a/packages/jira-adapter/test/adapter_creator.test.ts b/packages/jira-adapter/test/adapter_creator.test.ts index 88f37fb2cce..effc1fc620d 100644 --- a/packages/jira-adapter/test/adapter_creator.test.ts +++ b/packages/jira-adapter/test/adapter_creator.test.ts @@ -43,6 +43,7 @@ describe('adapter creator', () => { describe('with valid credentials', () => { let accountId: string beforeEach(async () => { + mockAxiosAdapter.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'FREE' }] }) mockAxiosAdapter.onGet().reply(200, { baseUrl: 'http://my_account.net' }); ({ accountId } = await adapter.validateCredentials( createCredentialsInstance({ baseUrl: 'http://my.net', user: 'u', token: 't' }) diff --git a/packages/jira-adapter/test/client/connection.test.ts b/packages/jira-adapter/test/client/connection.test.ts index 42b298d15e0..0a20544ca7e 100644 --- a/packages/jira-adapter/test/client/connection.test.ts +++ b/packages/jira-adapter/test/client/connection.test.ts @@ -27,6 +27,7 @@ describe('connection', () => { mockAxios = new MockAdapter(axios) mockAxios.onGet('/rest/api/3/configuration').reply(200) mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'http://my.jira.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'FREE' }] }) connection = await createConnection({ retries: 1 }).login( { baseUrl: 'http://myJira.net', user: 'me', token: 'tok', isDataCenter: false } ) @@ -78,6 +79,7 @@ describe('connection', () => { mockAxios = new MockAdapter(axios) mockAxios.onGet('/rest/api/3/configuration').reply(200) mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'http://my.jira.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'FREE' }] }) connection = await createConnection({ retries: 1 }).login( { baseUrl: 'http://myJira.net', user: 'me', token: 'tok', isDataCenter: false } ) @@ -102,6 +104,7 @@ describe('connection', () => { mockAxios = new MockAdapter(axios) mockAxios.onGet('/rest/api/3/configuration').reply(200) mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'http://my.jira.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'FREE' }] }) connection = await createConnection({ retries: 1 }).login( { baseUrl: 'http://myJira.net', user: 'me', token: 'tok', isDataCenter: true } ) @@ -115,9 +118,57 @@ describe('connection', () => { it('should not have force accept language headers when calling Jira DC', async () => { mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'http://my.jira.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'FREE' }] }) expect(mockAxios.history.get).toContainEqual(expect.objectContaining({ headers: expect.not.objectContaining(FORCE_ACCEPT_LANGUAGE_HEADERS), })) }) }) + describe('validate isProduction', () => { + let mockAxios: MockAdapter + let connection: clientUtils.APIConnection + beforeEach(async () => { + mockAxios = new MockAdapter(axios) + mockAxios.onGet('/rest/api/3/configuration').reply(200) + }) + afterEach(() => { + mockAxios.restore() + }) + it('should return isProduction undefined and accountType = undefined when account id does not include -sandbox- and has paid app', async () => { + connection = await createConnection({ retries: 1 }).login( + { baseUrl: 'http://myJira.net', user: 'me', token: 'tok', isDataCenter: true } + ) + mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'http://my.jira.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ id: 'software', plan: 'PAID' }, { id: 'serviceDesk', plan: 'FREE' }] }) + const { isProduction, accountType } = await validateCredentials({ + connection, + }) + expect(isProduction).toEqual(undefined) + expect(accountType).toEqual(undefined) + }) + it('should return isProduction false and accountType = "Sandbox" when account id includes -sandbox-', async () => { + connection = await createConnection({ retries: 1 }).login( + { baseUrl: 'https://test-sandbox-999.atlassian.net', user: 'me', token: 'tok', isDataCenter: true } + ) + mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'https://test-sandbox-999.atlassian.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'PAID' }] }) + const { isProduction, accountType } = await validateCredentials({ + connection, + }) + expect(isProduction).toEqual(false) + expect(accountType).toEqual('Sandbox') + }) + it('should return isProduction false and accountType = undefined when account id does not include -sandbox- but has no paid app', async () => { + connection = await createConnection({ retries: 1 }).login( + { baseUrl: 'https://test-sandbox-999.atlassian.net', user: 'me', token: 'tok', isDataCenter: true } + ) + mockAxios.onGet('/rest/api/3/serverInfo').reply(200, { baseUrl: 'https://test.atlassian.net' }) + mockAxios.onGet('/rest/api/3/instance/license').reply(200, { applications: [{ plan: 'FREE' }] }) + const { isProduction, accountType } = await validateCredentials({ + connection, + }) + expect(accountType).toEqual(undefined) + expect(isProduction).toEqual(false) + }) + }) })