diff --git a/api/src/organization/objects/__tests__/organization-summary.test.js b/api/src/organization/objects/__tests__/organization-summary.test.js index ee4ce5a9b5..273661e993 100644 --- a/api/src/organization/objects/__tests__/organization-summary.test.js +++ b/api/src/organization/objects/__tests__/organization-summary.test.js @@ -33,6 +33,30 @@ describe('given the organization summary object', () => { expect(demoType).toHaveProperty('dmarcPhase') expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) }) + it('has a webConnections field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('webConnections') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a ssl field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('ssl') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a spf field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('spf') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a dkim field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('dkim') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) }) describe('field resolvers', () => { @@ -48,10 +72,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ @@ -83,10 +104,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ @@ -120,10 +138,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ @@ -155,10 +170,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ @@ -191,10 +203,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ @@ -226,10 +235,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ @@ -262,10 +268,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ @@ -296,10 +299,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ @@ -344,13 +344,7 @@ describe('given the organization summary object', () => { .mockReturnValueOnce('maintain'), } - expect( - demoType.dmarcPhase.resolve( - { dmarc_phase: dmarcPhase }, - {}, - { i18n }, - ), - ).toEqual({ + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ categories: [ { count: 0, @@ -406,13 +400,7 @@ describe('given the organization summary object', () => { .mockReturnValueOnce('maintain'), } - expect( - demoType.dmarcPhase.resolve( - { dmarc_phase: dmarcPhase }, - {}, - { i18n }, - ), - ).toEqual({ + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ categories: [ { count: 50, diff --git a/api/src/organization/objects/organization-summary.js b/api/src/organization/objects/organization-summary.js index 68e00334f6..8380965061 100644 --- a/api/src/organization/objects/organization-summary.js +++ b/api/src/organization/objects/organization-summary.js @@ -8,8 +8,7 @@ export const organizationSummaryType = new GraphQLObjectType({ fields: () => ({ dmarc: { type: categorizedSummaryType, - description: - 'Summary based on DMARC scan results for a given organization.', + description: 'Summary based on DMARC scan results for a given organization.', resolve: ({ dmarc }, _) => { let percentPass, percentageFail if (dmarc.total <= 0) { @@ -41,8 +40,7 @@ export const organizationSummaryType = new GraphQLObjectType({ }, https: { type: categorizedSummaryType, - description: - 'Summary based on HTTPS scan results for a given organization.', + description: 'Summary based on HTTPS scan results for a given organization.', resolve: ({ https }, _) => { let percentPass, percentageFail if (https.total <= 0) { @@ -74,8 +72,7 @@ export const organizationSummaryType = new GraphQLObjectType({ }, mail: { type: categorizedSummaryType, - description: - 'Summary based on mail scan results for a given organization.', + description: 'Summary based on mail scan results for a given organization.', resolve: ({ mail }, _) => { let percentPass, percentageFail if (mail.total <= 0) { @@ -107,8 +104,7 @@ export const organizationSummaryType = new GraphQLObjectType({ }, web: { type: categorizedSummaryType, - description: - 'Summary based on web scan results for a given organization.', + description: 'Summary based on web scan results for a given organization.', resolve: ({ web }, _) => { let percentPass, percentageFail if (web.total <= 0) { @@ -142,35 +138,19 @@ export const organizationSummaryType = new GraphQLObjectType({ type: categorizedSummaryType, description: 'Summary based on DMARC phases for a given organization.', resolve: ({ dmarc_phase }, _) => { - let percentNotImplemented, - percentAsses, - percentDeploy, - percentEnforce, - percentMaintain + let percentNotImplemented, percentAssess, percentDeploy, percentEnforce, percentMaintain if (dmarc_phase.total <= 0) { percentNotImplemented = 0 - percentAsses = 0 + percentAssess = 0 percentDeploy = 0 percentEnforce = 0 percentMaintain = 0 } else { - percentNotImplemented = Number( - ((dmarc_phase.not_implemented / dmarc_phase.total) * 100).toFixed( - 1, - ), - ) - percentAsses = Number( - ((dmarc_phase.assess / dmarc_phase.total) * 100).toFixed(1), - ) - percentDeploy = Number( - ((dmarc_phase.deploy / dmarc_phase.total) * 100).toFixed(1), - ) - percentEnforce = Number( - ((dmarc_phase.enforce / dmarc_phase.total) * 100).toFixed(1), - ) - percentMaintain = Number( - ((dmarc_phase.maintain / dmarc_phase.total) * 100).toFixed(1), - ) + percentNotImplemented = Number(((dmarc_phase.not_implemented / dmarc_phase.total) * 100).toFixed(1)) + percentAssess = Number(((dmarc_phase.assess / dmarc_phase.total) * 100).toFixed(1)) + percentDeploy = Number(((dmarc_phase.deploy / dmarc_phase.total) * 100).toFixed(1)) + percentEnforce = Number(((dmarc_phase.enforce / dmarc_phase.total) * 100).toFixed(1)) + percentMaintain = Number(((dmarc_phase.maintain / dmarc_phase.total) * 100).toFixed(1)) } const categories = [ @@ -182,7 +162,7 @@ export const organizationSummaryType = new GraphQLObjectType({ { name: 'assess', count: dmarc_phase.assess, - percentage: percentAsses, + percentage: percentAssess, }, { name: 'deploy', @@ -207,5 +187,207 @@ export const organizationSummaryType = new GraphQLObjectType({ } }, }, + ssl: { + type: categorizedSummaryType, + description: 'Summary based on SSL scan results for a given organization.', + resolve: ({ ssl }, _) => { + let percentPass, percentageFail + if (ssl.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((ssl.pass / ssl.total) * 100).toFixed(1)) + percentageFail = Number(((ssl.fail / ssl.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: ssl.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: ssl.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: ssl.total, + } + }, + }, + webConnections: { + type: categorizedSummaryType, + description: 'Summary based on HTTPS and HSTS scan results for a given organization.', + resolve: ({ web_connections }, _) => { + let percentPass, percentageFail + if (web_connections.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((web_connections.pass / web_connections.total) * 100).toFixed(1)) + percentageFail = Number(((web_connections.fail / web_connections.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: web_connections.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: web_connections.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: web_connections.total, + } + }, + }, + spf: { + type: categorizedSummaryType, + description: 'Summary based on SPF scan results for a given organization.', + resolve: ({ spf }, _) => { + let percentPass, percentageFail + if (spf.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((spf.pass / spf.total) * 100).toFixed(1)) + percentageFail = Number(((spf.fail / spf.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: spf.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: spf.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: spf.total, + } + }, + }, + dkim: { + type: categorizedSummaryType, + description: 'Summary based on DKIM scan results for a given organization.', + resolve: ({ dkim }, _) => { + let percentPass, percentageFail + if (dkim.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((dkim.pass / dkim.total) * 100).toFixed(1)) + percentageFail = Number(((dkim.fail / dkim.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: dkim.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: dkim.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: dkim.total, + } + }, + }, + httpsIncludeHidden: { + type: categorizedSummaryType, + description: + 'Summary based on HTTPS scan results for a given organization that includes domains marked as hidden.', + resolve: ({ https, hidden }, _) => { + const pass = https.pass + hidden.https.pass + const fail = https.fail + hidden.https.fail + const total = https.total + hidden.https.total + + let percentPass, percentageFail + if (total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((pass / total) * 100).toFixed(1)) + percentageFail = Number(((fail / total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: pass, + percentage: percentPass, + }, + { + name: 'fail', + count: fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total, + } + }, + }, + dmarcIncludeHidden: { + type: categorizedSummaryType, + description: + 'Summary based on HTTPS scan results for a given organization that includes domains marked as hidden.', + resolve: ({ dmarc, hidden }, _) => { + const pass = dmarc.pass + hidden.dmarc.pass + const fail = dmarc.fail + hidden.dmarc.fail + const total = dmarc.total + hidden.https.total + + let percentPass, percentageFail + if (total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((pass / total) * 100).toFixed(1)) + percentageFail = Number(((fail / total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: pass, + percentage: percentPass, + }, + { + name: 'fail', + count: fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total, + } + }, + }, }), }) diff --git a/api/src/summaries/queries/__tests__/dkim-summary.test.js b/api/src/summaries/queries/__tests__/dkim-summary.test.js new file mode 100644 index 0000000000..1865d8cbb8 --- /dev/null +++ b/api/src/summaries/queries/__tests__/dkim-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given dkimSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful dkim summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'dkim', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns dkim summary', async () => { + const response = await graphql( + schema, + ` + query { + dkimSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + dkimSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful dkim summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + dkimSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load DKIM summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve DKIM summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/dmarc-summary.test.js b/api/src/summaries/queries/__tests__/dmarc-summary.test.js new file mode 100644 index 0000000000..395a557675 --- /dev/null +++ b/api/src/summaries/queries/__tests__/dmarc-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given dmarcSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful dmarc summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'dmarc', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns dmarc summary', async () => { + const response = await graphql( + schema, + ` + query { + dmarcSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + dmarcSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful dmarc summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + dmarcSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load DMARC summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve DMARC summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/spf-summary.test.js b/api/src/summaries/queries/__tests__/spf-summary.test.js new file mode 100644 index 0000000000..74b12f6753 --- /dev/null +++ b/api/src/summaries/queries/__tests__/spf-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given spfSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful spf summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'spf', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns spf summary', async () => { + const response = await graphql( + schema, + ` + query { + spfSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + spfSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful spf summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + spfSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load SPF summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve SPF summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/ssl-summary.test.js b/api/src/summaries/queries/__tests__/ssl-summary.test.js new file mode 100644 index 0000000000..178c5d2a2e --- /dev/null +++ b/api/src/summaries/queries/__tests__/ssl-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given sslSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful ssl summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'ssl', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns ssl summary', async () => { + const response = await graphql( + schema, + ` + query { + sslSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + sslSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful ssl summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + sslSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load SSL summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve SSL summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/web-connections-summary.test.js b/api/src/summaries/queries/__tests__/web-connections-summary.test.js new file mode 100644 index 0000000000..e216a8c19a --- /dev/null +++ b/api/src/summaries/queries/__tests__/web-connections-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given webConnectionsSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful webConnections summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'web_connections', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns webConnections summary', async () => { + const response = await graphql( + schema, + ` + query { + webConnectionsSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + webConnectionsSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful webConnections summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + webConnectionsSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load web connections summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve web connections summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/dkim-summary.js b/api/src/summaries/queries/dkim-summary.js new file mode 100644 index 0000000000..9a91978ce4 --- /dev/null +++ b/api/src/summaries/queries/dkim-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const dkimSummary = { + type: categorizedSummaryType, + description: 'DKIM summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('dkim') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve DKIM summary.`) + throw new Error(i18n._(t`Unable to load DKIM summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/dmarc-summary.js b/api/src/summaries/queries/dmarc-summary.js new file mode 100644 index 0000000000..e7ca64b3c8 --- /dev/null +++ b/api/src/summaries/queries/dmarc-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const dmarcSummary = { + type: categorizedSummaryType, + description: 'DMARC summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('dmarc') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve DMARC summary.`) + throw new Error(i18n._(t`Unable to load DMARC summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/index.js b/api/src/summaries/queries/index.js index 6b51658f0f..c4a8068b9a 100644 --- a/api/src/summaries/queries/index.js +++ b/api/src/summaries/queries/index.js @@ -1,4 +1,9 @@ -export * from './mail-summary' -export * from './web-summary' +export * from './dkim-summary' export * from './dmarc-phase-summary' +export * from './dmarc-summary' export * from './https-summary' +export * from './mail-summary' +export * from './spf-summary' +export * from './ssl-summary' +export * from './web-connections-summary' +export * from './web-summary' diff --git a/api/src/summaries/queries/spf-summary.js b/api/src/summaries/queries/spf-summary.js new file mode 100644 index 0000000000..c320ff40ff --- /dev/null +++ b/api/src/summaries/queries/spf-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const spfSummary = { + type: categorizedSummaryType, + description: 'SPF summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('spf') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve SPF summary.`) + throw new Error(i18n._(t`Unable to load SPF summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/ssl-summary.js b/api/src/summaries/queries/ssl-summary.js new file mode 100644 index 0000000000..ceafecefde --- /dev/null +++ b/api/src/summaries/queries/ssl-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const sslSummary = { + type: categorizedSummaryType, + description: 'SSL summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('ssl') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve SSL summary.`) + throw new Error(i18n._(t`Unable to load SSL summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/web-connections-summary.js b/api/src/summaries/queries/web-connections-summary.js new file mode 100644 index 0000000000..a25acc7d38 --- /dev/null +++ b/api/src/summaries/queries/web-connections-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const webConnectionsSummary = { + type: categorizedSummaryType, + description: 'Web connections (HTTPS + HSTS) summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('web_connections') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve web connections summary.`) + throw new Error(i18n._(t`Unable to load web connections summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/user/loaders/load-my-tracker-by-user-id.js b/api/src/user/loaders/load-my-tracker-by-user-id.js index a88d42b57b..559bdf601d 100644 --- a/api/src/user/loaders/load-my-tracker-by-user-id.js +++ b/api/src/user/loaders/load-my-tracker-by-user-id.js @@ -21,7 +21,8 @@ export const loadMyTrackerByUserId = id: domain._key, _type: "domain", "phase": domain.phase, - "httpsStatus": domain.status.https + "https": domain.status.https, + "dmarc": domain.status.dmarc } ) RETURN { "domains": favDomains } @@ -49,6 +50,11 @@ export const loadMyTrackerByUserId = fail: 0, total: 0, }, + dmarc: { + pass: 0, + fail: 0, + total: 0, + }, dmarc_phase: { not_implemented: 0, assess: 0, @@ -59,16 +65,21 @@ export const loadMyTrackerByUserId = }, } - domainsInfo.domains.forEach(({ phase, httpsStatus }) => { + domainsInfo.domains.forEach(({ phase, https, dmarc }) => { // calculate https summary - if (httpsStatus === 'pass') { + if (https === 'pass') { returnSummaries.https.pass++ returnSummaries.https.total++ - } else if (httpsStatus === 'fail') { + } else if (https === 'fail') { returnSummaries.https.fail++ returnSummaries.https.total++ } + // calculate DMARC summary + if (dmarc === 'pass') returnSummaries.dmarc.pass++ + else if (dmarc === 'fail') returnSummaries.dmarc.fail++ + returnSummaries.dmarc.total++ + // calculate dmarcPhase summary if (phase === 'not implemented') returnSummaries.dmarc_phase.not_implemented++ else if (phase === 'assess') returnSummaries.dmarc_phase.assess++ diff --git a/frontend/mocking/faked_schema.js b/frontend/mocking/faked_schema.js index 5c60a562c8..509f47838e 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -155,18 +155,33 @@ export const getTypeNames = () => gql` # CSV formatted output of all domains in all organizations including their email and web scan statuses. getAllOrganizationDomainStatuses: String - # Email summary computed values, used to build summary cards. - mailSummary: CategorizedSummary - - # Web summary computed values, used to build summary cards. - webSummary: CategorizedSummary + # DKIM summary computed values, used to build summary cards. + dkimSummary: CategorizedSummary # DMARC phase summary computed values, used to build summary cards. dmarcPhaseSummary: CategorizedSummary + # DMARC summary computed values, used to build summary cards. + dmarcSummary: CategorizedSummary + # HTTPS summary computed values, used to build summary cards. httpsSummary: CategorizedSummary + # Email summary computed values, used to build summary cards. + mailSummary: CategorizedSummary + + # SPF summary computed values, used to build summary cards. + spfSummary: CategorizedSummary + + # SSL summary computed values, used to build summary cards. + sslSummary: CategorizedSummary + + # SSL summary computed values, used to build summary cards. + webConnectionsSummary: CategorizedSummary + + # Web summary computed values, used to build summary cards. + webSummary: CategorizedSummary + # Query the currently logged in user. findMe: PersonalUser @@ -847,6 +862,24 @@ export const getTypeNames = () => gql` # Summary based on DMARC phases for a given organization. dmarcPhase: CategorizedSummary + + # Summary based on SSL scan results for a given organization. + ssl: CategorizedSummary + + # Summary based on HTTPS and HSTS scan results for a given organization. + webConnections: CategorizedSummary + + # Summary based on SPF scan results for a given organization. + spf: CategorizedSummary + + # Summary based on DKIM scan results for a given organization. + dkim: CategorizedSummary + + # Summary based on HTTPS scan results for a given organization that includes domains marked as hidden. + httpsIncludeHidden: CategorizedSummary + + # Summary based on HTTPS scan results for a given organization that includes domains marked as hidden. + dmarcIncludeHidden: CategorizedSummary } # This object contains the list of different categories for pre-computed diff --git a/frontend/mocking/mocker.js b/frontend/mocking/mocker.js index dc3f288554..c06e31d3fc 100644 --- a/frontend/mocking/mocker.js +++ b/frontend/mocking/mocker.js @@ -336,7 +336,7 @@ const mocks = { const tagId = 'tag' + faker.datatype.number({ min: 1, max: 14 }) const tagName = 'TAG-' + faker.helpers.randomize(['missing', 'downgraded', 'bad-chain', 'short-age', 'certificate-expired']) - const guidance = faker.lorem.sentence() + // const guidance = faker.lorem.sentence() const refLinks = [...new Array(1)] const refLinksTech = [...new Array(1)] @@ -356,6 +356,58 @@ const mocks = { totalCount: numberOfEdges, } }, + MyTrackerResult: () => { + const domainCount = faker.datatype.number({ min: 0, max: 500 }) + const httpsPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const httpsFailCount = domainCount - httpsPassCount + const httpsPassPercentage = (httpsPassCount / domainCount) * 100 + const httpsFailPercentage = 100 - httpsPassPercentage + const https = { + total: domainCount, + categories: [ + { + name: 'pass', + count: httpsPassCount, + percentage: httpsPassPercentage, + }, + { + name: 'fail', + count: httpsFailCount, + percentage: httpsFailPercentage, + }, + ], + } + + const mailPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const mailFailCount = domainCount - mailPassCount + const mailPassPercentage = (mailPassCount / domainCount) * 100 + const mailFailPercentage = 100 - mailPassPercentage + const dmarc = { + total: domainCount, + categories: [ + { + name: 'pass', + count: mailPassCount, + percentage: mailPassPercentage, + }, + { + name: 'fail', + count: mailFailCount, + percentage: mailFailPercentage, + }, + ], + } + + const dmarcPhase = dmarcPhaseSummaryMock() + return { + domainCount, + domains: { + edges: [...new Array(domainCount)], + totalCount: domainCount, + }, + summaries: { https, dmarc, dmarcPhase }, + } + }, Organization: () => { const name = faker.company.companyName() const slug = faker.helpers.slugify(name) @@ -600,6 +652,9 @@ const schemaWithMocks = addMocksToSchema({ findMyOrganizations: (_, args, _context, resolveInfo) => { return getConnectionObject(store, args, resolveInfo) }, + findMyTracker: (_, _args, _context, _resolveInfo) => { + return store.get('MyTrackerResult') + }, dmarcPhaseSummary: (_, _args, _context, _resolveInfo) => { return dmarcPhaseSummaryMock() }, diff --git a/frontend/src/graphql/fragments.js b/frontend/src/graphql/fragments.js index 0e1051b6b9..af0d10cb72 100644 --- a/frontend/src/graphql/fragments.js +++ b/frontend/src/graphql/fragments.js @@ -18,6 +18,21 @@ export const Authorization = { }, } +export const Summary = { + fragments: { + requiredFields: gql` + fragment RequiredSummaryFields on CategorizedSummary { + total + categories { + name + count + percentage + } + } + `, + }, +} + export const Guidance = { fragments: { requiredFields: gql` diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 81e20448db..ea7588da83 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -1,5 +1,5 @@ import { gql } from '@apollo/client' -import { Guidance, Status } from './fragments' +import { Guidance, Summary, Status } from './fragments' export const PAGINATED_ORGANIZATIONS = gql` query PaginatedOrganizations( @@ -56,25 +56,40 @@ export const PAGINATED_ORGANIZATIONS = gql` } ` -export const HTTPS_AND_DMARC_SUMMARY = gql` +export const LANDING_PAGE_SUMMARIES = gql` query LandingPageSummaries { + # Tier 1 httpsSummary { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + dmarcSummary { + ...RequiredSummaryFields + } + # Tier 2 + webConnectionsSummary { + ...RequiredSummaryFields + } + sslSummary { + ...RequiredSummaryFields + } + spfSummary { + ...RequiredSummaryFields + } + dkimSummary { + ...RequiredSummaryFields } dmarcPhaseSummary { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + # Tier 3 + webSummary { + ...RequiredSummaryFields + } + mailSummary { + ...RequiredSummaryFields } } + ${Summary.fragments.requiredFields} ` export const GET_ORGANIZATION_DOMAINS_STATUSES_CSV = gql` @@ -463,24 +478,42 @@ export const ORG_DETAILS_PAGE = gql` verified summaries { https { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + dmarc { + ...RequiredSummaryFields + } + httpsIncludeHidden { + ...RequiredSummaryFields + } + dmarcIncludeHidden { + ...RequiredSummaryFields + } + dkim { + ...RequiredSummaryFields + } + spf { + ...RequiredSummaryFields + } + ssl { + ...RequiredSummaryFields + } + webConnections { + ...RequiredSummaryFields } dmarcPhase { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + web { + ...RequiredSummaryFields + } + mail { + ...RequiredSummaryFields } } } } + ${Summary.fragments.requiredFields} ` export const PAGINATED_ORG_DOMAINS = gql` @@ -985,25 +1018,18 @@ export const MY_TRACKER_SUMMARY = gql` findMyTracker { summaries { https { - categories { - name - count - percentage - } - total + ...RequiredSummaryFields + } + dmarc { + ...RequiredSummaryFields } dmarcPhase { - categories { - name - count - percentage - } - total + ...RequiredSummaryFields } } - domainCount } } + ${Summary.fragments.requiredFields} ` export const MY_TRACKER_DOMAINS = gql` diff --git a/frontend/src/landing/LandingPage.js b/frontend/src/landing/LandingPage.js index 810a44efa4..e1d5177866 100644 --- a/frontend/src/landing/LandingPage.js +++ b/frontend/src/landing/LandingPage.js @@ -26,10 +26,8 @@ export function LandingPage({ loginRequired, isLoggedIn }) { - Canadians rely on the Government of Canada to provide secure digital - services. The Policy on Service and Digital guides government online - services to adopt good security practices for practices outlined in - the{' '} + Canadians rely on the Government of Canada to provide secure digital services. The Policy on Service and + Digital guides government online services to adopt good security practices for practices outlined in the{' '} if (error) return - return ( - - - - ) + const summaries = { + https: data?.httpsSummary, + dmarc: data?.dmarcSummary, + webConnections: data?.webConnectionsSummary, + ssl: data?.sslSummary, + spf: data?.spfSummary, + dkim: data?.dkimSummary, + dmarcPhase: data?.dmarcPhaseSummary, + web: data?.webSummary, + mail: data?.mailSummary, + } + + return } diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index 260f1ef592..160e701081 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -21,23 +21,23 @@ msgstr ", and" msgid ". Personal information will not be disclosed by Treasury Board Secretariat of Canada (TBS) except in accordance with the" msgstr ". Personal information will not be disclosed by Treasury Board Secretariat of Canada (TBS) except in accordance with the" -#: src/summaries/RadialBarChart.js:25 +#: src/summaries/RadialBarChart.js:30 msgid "0. Not Implemented" msgstr "0. Not Implemented" -#: src/summaries/RadialBarChart.js:27 +#: src/summaries/RadialBarChart.js:32 msgid "1. Assess" msgstr "1. Assess" -#: src/summaries/RadialBarChart.js:31 +#: src/summaries/RadialBarChart.js:36 msgid "2. Deploy" msgstr "2. Deploy" -#: src/summaries/RadialBarChart.js:35 +#: src/summaries/RadialBarChart.js:40 msgid "3. Enforce" msgstr "3. Enforce" -#: src/summaries/RadialBarChart.js:39 +#: src/summaries/RadialBarChart.js:44 msgid "4. Maintain" msgstr "4. Maintain" @@ -65,7 +65,7 @@ msgstr "A DNS request for this service has resulted in the following error code: msgid "A domain may only be removed for one of the reasons below. For a domain to no longer exist, it must be removed from the DNS. If you need to remove this domain for a different reason, please contact TBS Cyber Security." msgstr "A domain may only be removed for one of the reasons below. For a domain to no longer exist, it must be removed from the DNS. If you need to remove this domain for a different reason, please contact TBS Cyber Security." -#: src/summaries/SummaryGroup.js:47 +#: src/summaries/TierOneSummaries.js:18 msgid "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" msgstr "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" @@ -533,11 +533,19 @@ msgstr "Collect and analyze DMARC reports." msgid "Comparison" msgstr "Comparison" -#: src/summaries/SummaryGroup.js:24 +#: src/summaries/SummaryGroup.js:20 msgid "Compliant" msgstr "Compliant" -#: src/admin/AdminDomainModal.js:391 +#: src/summaries/TierThreeSummaries.js:18 +msgid "Configuration requirements for email services completely met" +msgstr "Configuration requirements for email services completely met" + +#: src/summaries/TierThreeSummaries.js:12 +msgid "Configuration requirements for web sites and services completely met" +msgstr "Configuration requirements for web sites and services completely met" + +#: src/admin/AdminDomainModal.js:459 #: src/admin/AdminDomains.js:386 #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 @@ -671,7 +679,7 @@ msgstr "Current Phone Number:" #: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 #: src/organizationDetails/OrganizationDomains.js:269 -#: src/organizationDetails/OrganizationDomains.js:313 +#: src/organizationDetails/OrganizationDomains.js:314 msgid "Curves" msgstr "Curves" @@ -724,6 +732,14 @@ msgstr "DKIM Selectors:" msgid "DKIM Status" msgstr "DKIM Status" +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM Summary" +msgstr "DKIM Summary" + +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM record and keys are deployed and valid" +msgstr "DKIM record and keys are deployed and valid" + #: src/guidance/EmailGuidance.js:218 #~ msgid "DKIM record could not be found for this selector." #~ msgstr "DKIM record could not be found for this selector." @@ -737,11 +753,11 @@ msgstr "DMARC" msgid "DMARC Configuration" msgstr "DMARC Configuration" -#: src/summaries/SummaryGroup.js:46 +#: src/summaries/TierOneSummaries.js:17 msgid "DMARC Configuration Summary" msgstr "DMARC Configuration Summary" -#: src/organizations/OrganizationCard.js:130 +#: src/organizations/OrganizationCard.js:93 msgid "DMARC Configured" msgstr "DMARC Configured" @@ -760,8 +776,8 @@ msgstr "DMARC Failures by IP Address" msgid "DMARC Implementation Phase: {0}" msgstr "DMARC Implementation Phase: {0}" -#: src/organizationDetails/OrganizationDetails.js:135 -#: src/user/MyTrackerPage.js:96 +#: src/organizationDetails/OrganizationDetails.js:126 +#: src/user/MyTrackerPage.js:79 msgid "DMARC Phases" msgstr "DMARC Phases" @@ -788,10 +804,18 @@ msgstr "DMARC Status" msgid "DMARC Summaries" msgstr "DMARC Summaries" +#: src/summaries/TierTwoSummaries.js:56 +msgid "DMARC Summary" +msgstr "DMARC Summary" + #: src/summaries/SummaryGroup.js:47 #~ msgid "DMARC phase summary" #~ msgstr "DMARC phase summary" +#: src/summaries/TierTwoSummaries.js:57 +msgid "DMARC policy of quarantine or reject, and all messages from non-mail domain is rejected" +msgstr "DMARC policy of quarantine or reject, and all messages from non-mail domain is rejected" + #: src/dmarc/DmarcReportPage.js:185 #: src/guidance/EmailGuidance.js:272 #~ msgid "DMARC record could not be found during the scan." @@ -963,11 +987,11 @@ msgstr "Domain:" #: src/app/FloatingMenu.js:116 #: src/domains/DomainsPage.js:82 #: src/domains/DomainsPage.js:116 -#: src/organizationDetails/OrganizationDetails.js:138 +#: src/organizationDetails/OrganizationDetails.js:129 #: src/organizationDetails/OrganizationDomains.js:106 #: src/summaries/Doughnut.js:50 #: src/summaries/Doughnut.js:75 -#: src/user/MyTrackerPage.js:99 +#: src/user/MyTrackerPage.js:82 msgid "Domains" msgstr "Domains" @@ -1058,6 +1082,10 @@ msgstr "Email Security:" msgid "Email Sent" msgstr "Email Sent" +#: src/summaries/TierThreeSummaries.js:17 +msgid "Email Summary" +msgstr "Email Summary" + #: src/user/EditableUserTFAMethod.js:111 msgid "Email Validated" msgstr "Email Validated" @@ -1347,7 +1375,7 @@ msgstr "Government of Canada Employees" #~ msgid "Graph direction:" #~ msgstr "Graph direction:" -#: src/app/App.js:345 +#: src/app/App.js:350 #: src/dmarc/DmarcReportPage.js:196 #: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" @@ -1427,12 +1455,12 @@ msgstr "HTTPS" msgid "HTTPS (443) Chain" msgstr "HTTPS (443) Chain" -#: src/summaries/SummaryGroup.js:16 +#: src/summaries/TierOneSummaries.js:11 msgid "HTTPS Configuration Summary" msgstr "HTTPS Configuration Summary" -#: src/organizations/OrganizationCard.js:118 -#: src/organizations/Organizations.js:135 +#: src/organizations/OrganizationCard.js:85 +#: src/organizations/Organizations.js:122 msgid "HTTPS Configured" msgstr "HTTPS Configured" @@ -1453,7 +1481,7 @@ msgstr "HTTPS Scan Complete" msgid "HTTPS Status" msgstr "HTTPS Status" -#: src/summaries/SummaryGroup.js:17 +#: src/summaries/TierOneSummaries.js:12 msgid "HTTPS is configured and HTTP connections redirect to HTTPS" msgstr "HTTPS is configured and HTTP connections redirect to HTTPS" @@ -1461,6 +1489,10 @@ msgstr "HTTPS is configured and HTTP connections redirect to HTTPS" #~ msgid "HTTPS is configured and HTTP connections redirect to HTTPS (ITPIN 6.1.1)" #~ msgstr "HTTPS is configured and HTTP connections redirect to HTTPS (ITPIN 6.1.1)" +#: src/summaries/TierTwoSummaries.js:40 +msgid "HTTPS is configured, HTTP redirects, and HSTS is enabled" +msgstr "HTTPS is configured, HTTP redirects, and HSTS is enabled" + #: src/app/RequestScanNotificationHandler.js:90 msgid "HTTPS scan for domain \"{0}\" has completed." msgstr "HTTPS scan for domain \"{0}\" has completed." @@ -1618,7 +1650,7 @@ msgstr "Implementation: <0>Guidance on securely configuring network protocols (I msgid "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" msgstr "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" -#: src/summaries/SummaryGroup.js:58 +#: src/summaries/SummaryGroup.js:31 msgid "Implemented" msgstr "Implemented" @@ -1626,6 +1658,11 @@ msgstr "Implemented" msgid "Inactive" msgstr "Inactive" +#: src/summaries/TieredSummaries.js:55 +#: src/summaries/TieredSummaries.js:57 +msgid "Include hidden domains in summaries." +msgstr "Include hidden domains in summaries." + #: src/auth/TwoFactorAuthenticatePage.js:81 msgid "Incorrect authenticate.result typename." msgstr "Incorrect authenticate.result typename." @@ -2102,8 +2139,8 @@ msgid "No DKIM selectors are currently attached to this domain. Please contact a msgstr "No DKIM selectors are currently attached to this domain. Please contact an admin of an affiliated organization to add selectors." #: src/summaries/SummaryGroup.js:67 -msgid "No DMARC phase information available for this organization." -msgstr "No DMARC phase information available for this organization." +#~ msgid "No DMARC phase information available for this organization." +#~ msgstr "No DMARC phase information available for this organization." #: src/admin/AdminDomains.js:156 #: src/domains/DomainsPage.js:89 @@ -2112,11 +2149,11 @@ msgid "No Domains" msgstr "No Domains" #: src/summaries/SummaryGroup.js:37 -msgid "No HTTPS configuration information available for this organization." -msgstr "No HTTPS configuration information available for this organization." +#~ msgid "No HTTPS configuration information available for this organization." +#~ msgstr "No HTTPS configuration information available for this organization." #: src/admin/WebCheckPage.js:94 -#: src/organizations/Organizations.js:78 +#: src/organizations/Organizations.js:81 msgid "No Organizations" msgstr "No Organizations" @@ -2194,7 +2231,7 @@ msgstr "No users" msgid "No values were supplied when attempting to update organization details." msgstr "No values were supplied when attempting to update organization details." -#: src/summaries/SummaryGroup.js:20 +#: src/summaries/SummaryGroup.js:16 msgid "Non-compliant" msgstr "Non-compliant" @@ -2211,7 +2248,7 @@ msgstr "Not After:" msgid "Not Before:" msgstr "Not Before:" -#: src/summaries/SummaryGroup.js:50 +#: src/summaries/SummaryGroup.js:27 msgid "Not Implemented" msgstr "Not Implemented" @@ -2282,7 +2319,7 @@ msgstr "Options include contacting the <0>SSC WebSSL services team and/or us msgid "Organization" msgstr "Organization" -#: src/organizationDetails/OrganizationDetails.js:67 +#: src/organizationDetails/OrganizationDetails.js:58 msgid "Organization Details" msgstr "Organization Details" @@ -2525,7 +2562,7 @@ msgstr "Protect domains that do not send email - GOV.UK (www.gov.uk)" #: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 #: src/organizationDetails/OrganizationDomains.js:275 -#: src/organizationDetails/OrganizationDomains.js:314 +#: src/organizationDetails/OrganizationDomains.js:315 msgid "Protocols" msgstr "Protocols" @@ -2667,7 +2704,7 @@ msgstr "Requirements: <0>Email Management Services Configuration RequirementsWeb Sites and Services Management Configuration Requirements" msgstr "Requirements: <0>Web Sites and Services Management Configuration Requirements" -#: src/app/App.js:182 +#: src/app/App.js:187 msgid "Reset Password" msgstr "Reset Password" @@ -2759,10 +2796,18 @@ msgstr "SPF Results" msgid "SPF Status" msgstr "SPF Status" +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF Summary" +msgstr "SPF Summary" + #: src/guidance/EmailGuidance.js:167 #~ msgid "SPF record could not be found during the scan." #~ msgstr "SPF record could not be found during the scan." +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF record is deployed and valid" +msgstr "SPF record is deployed and valid" + #: src/app/RequestScanNotificationHandler.js:72 #~ msgid "SSL Scan Complete" #~ msgstr "SSL Scan Complete" @@ -2923,7 +2968,7 @@ msgstr "Serial:" msgid "Services" msgstr "Services" -#: src/organizations/OrganizationCard.js:107 +#: src/organizations/OrganizationCard.js:79 msgid "Services: {domainCount}" msgstr "Services: {domainCount}" @@ -3190,8 +3235,8 @@ msgstr "Submit" msgid "Successfully removed user {0}." msgstr "Successfully removed user {0}." -#: src/organizationDetails/OrganizationDetails.js:132 -#: src/user/MyTrackerPage.js:93 +#: src/organizationDetails/OrganizationDetails.js:123 +#: src/user/MyTrackerPage.js:76 msgid "Summary" msgstr "Summary" @@ -3239,6 +3284,10 @@ msgstr "TLS Results" msgid "TLS Scan Complete" msgstr "TLS Scan Complete" +#: src/summaries/TierTwoSummaries.js:45 +msgid "TLS Summary" +msgstr "TLS Summary" + #: src/app/RequestScanNotificationHandler.js:73 msgid "TLS scan for domain \"{0}\" has completed." msgstr "TLS scan for domain \"{0}\" has completed." @@ -3413,6 +3462,18 @@ msgstr "This service is not web-hosting and does not require compliance with the msgid "This user is not affiliated with any organizations" msgstr "This user is not affiliated with any organizations" +#: src/summaries/TieredSummaries.js:48 +msgid "Tier 1: Minimum Requirements" +msgstr "Tier 1: Minimum Requirements" + +#: src/summaries/TieredSummaries.js:75 +msgid "Tier 2: Improved Posture" +msgstr "Tier 2: Improved Posture" + +#: src/summaries/TieredSummaries.js:92 +msgid "Tier 3: Compliance" +msgstr "Tier 3: Compliance" + #: src/admin/AuditLogTable.js:70 msgid "Time Generated" msgstr "Time Generated" @@ -3730,8 +3791,8 @@ msgid "User:" msgstr "User:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:24 -#: src/organizationDetails/OrganizationDetails.js:142 +#: src/admin/AdminPanel.js:31 +#: src/organizationDetails/OrganizationDetails.js:133 msgid "Users" msgstr "Users" @@ -3773,7 +3834,7 @@ msgstr "Verify Account" #~ msgid "Vertical View" #~ msgstr "Vertical View" -#: src/organizations/OrganizationCard.js:144 +#: src/organizations/OrganizationCard.js:101 msgid "View Details" msgstr "View Details" @@ -3841,6 +3902,10 @@ msgstr "Web (HTTPS/TLS)" msgid "Web Check" msgstr "Web Check" +#: src/summaries/TierTwoSummaries.js:39 +msgid "Web Connections Summary" +msgstr "Web Connections Summary" + #: src/domains/ScanDomain.js:242 #: src/guidance/GuidancePage.js:102 msgid "Web Guidance" @@ -3858,6 +3923,10 @@ msgstr "Web Security:" #~ msgid "Web Sites and Services Management Configuration Requirements Compliant" #~ msgstr "Web Sites and Services Management Configuration Requirements Compliant" +#: src/summaries/TierThreeSummaries.js:11 +msgid "Web Summary" +msgstr "Web Summary" + #: src/summaries/Doughnut.js:34 msgid "Web-hosting" msgstr "Web-hosting" @@ -3874,7 +3943,7 @@ msgstr "Web-hosting" msgid "Welcome to Tracker, please enter your details." msgstr "Welcome to Tracker, please enter your details." -#: src/user/MyTrackerPage.js:78 +#: src/user/MyTrackerPage.js:63 msgid "Welcome to your personal view of Tracker. Moderate the security posture of domains of interest across multiple organizations. To add domains to this view, use the star icon buttons available on domain lists." msgstr "Welcome to your personal view of Tracker. Moderate the security posture of domains of interest across multiple organizations. To add domains to this view, use the star icon buttons available on domain lists." @@ -4068,10 +4137,10 @@ msgstr "contact us" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:100 -#: src/app/App.js:279 -#: src/user/MyTrackerPage.js:43 -#: src/user/MyTrackerPage.js:74 +#: src/app/App.js:105 +#: src/app/App.js:284 +#: src/user/MyTrackerPage.js:33 +#: src/user/MyTrackerPage.js:59 msgid "myTracker" msgstr "myTracker" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index 141e371a9c..65f155fba8 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -21,23 +21,23 @@ msgstr ", et" msgid ". Personal information will not be disclosed by Treasury Board Secretariat of Canada (TBS) except in accordance with the" msgstr ". Les renseignements personnels ne seront pas divulgués par le Secrétariat du Conseil du Trésor du Canada (SCT), sauf en conformité avec les dispositions du" -#: src/summaries/RadialBarChart.js:25 +#: src/summaries/RadialBarChart.js:30 msgid "0. Not Implemented" msgstr "0. Non mis en œuvre" -#: src/summaries/RadialBarChart.js:27 +#: src/summaries/RadialBarChart.js:32 msgid "1. Assess" msgstr "1. Évaluez" -#: src/summaries/RadialBarChart.js:31 +#: src/summaries/RadialBarChart.js:36 msgid "2. Deploy" msgstr "2. Déployer" -#: src/summaries/RadialBarChart.js:35 +#: src/summaries/RadialBarChart.js:40 msgid "3. Enforce" msgstr "3. Appliquer" -#: src/summaries/RadialBarChart.js:39 +#: src/summaries/RadialBarChart.js:44 msgid "4. Maintain" msgstr "4. Maintenir" @@ -65,7 +65,7 @@ msgstr "Une requête DNS pour ce service a donné lieu au code d'erreur suivant msgid "A domain may only be removed for one of the reasons below. For a domain to no longer exist, it must be removed from the DNS. If you need to remove this domain for a different reason, please contact TBS Cyber Security." msgstr "Un domaine ne peut être supprimé que pour l'une des raisons ci-dessous. Pour qu'un domaine n'existe plus, il doit être supprimé du DNS. Si vous devez supprimer ce domaine pour une autre raison, veuillez contacter TBS Cyber Security." -#: src/summaries/SummaryGroup.js:47 +#: src/summaries/TierOneSummaries.js:18 msgid "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" msgstr "Une politique DMARC minimale de \"p=none\" avec au moins une adresse définie comme destinataire des rapports agrégés." @@ -533,11 +533,19 @@ msgstr "Recueillir et analyser les rapports DMARC." msgid "Comparison" msgstr "Comparaison" -#: src/summaries/SummaryGroup.js:24 +#: src/summaries/SummaryGroup.js:20 msgid "Compliant" msgstr "Conforme" -#: src/admin/AdminDomainModal.js:391 +#: src/summaries/TierThreeSummaries.js:18 +msgid "Configuration requirements for email services completely met" +msgstr "Les exigences de configuration pour les services de courrier électronique sont entièrement satisfaites" + +#: src/summaries/TierThreeSummaries.js:12 +msgid "Configuration requirements for web sites and services completely met" +msgstr "Les exigences de configuration des sites et services web sont entièrement satisfaites" + +#: src/admin/AdminDomainModal.js:459 #: src/admin/AdminDomains.js:386 #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 @@ -671,7 +679,7 @@ msgstr "Numéro de téléphone actuel:" #: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 #: src/organizationDetails/OrganizationDomains.js:269 -#: src/organizationDetails/OrganizationDomains.js:313 +#: src/organizationDetails/OrganizationDomains.js:314 msgid "Curves" msgstr "Courbes" @@ -724,6 +732,14 @@ msgstr "Sélecteurs DKIM:" msgid "DKIM Status" msgstr "Statut DKIM" +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM Summary" +msgstr "Résumé DKIM" + +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM record and keys are deployed and valid" +msgstr "L'enregistrement DKIM et les clés sont déployés et valides" + #: src/guidance/EmailGuidance.js:218 #~ msgid "DKIM record could not be found for this selector." #~ msgstr "Un enregistrement DKIM n'a pas pu être trouvé pour ce sélecteur." @@ -737,11 +753,11 @@ msgstr "DMARC" msgid "DMARC Configuration" msgstr "Configuration de DMARC" -#: src/summaries/SummaryGroup.js:46 +#: src/summaries/TierOneSummaries.js:17 msgid "DMARC Configuration Summary" msgstr "Résumé de la configuration DMARC" -#: src/organizations/OrganizationCard.js:130 +#: src/organizations/OrganizationCard.js:93 msgid "DMARC Configured" msgstr "DMARC configuré" @@ -760,8 +776,8 @@ msgstr "Défaillances du DMARC par adresse IP" msgid "DMARC Implementation Phase: {0}" msgstr "Phase de mise en œuvre de DMARC: {0}" -#: src/organizationDetails/OrganizationDetails.js:135 -#: src/user/MyTrackerPage.js:96 +#: src/organizationDetails/OrganizationDetails.js:126 +#: src/user/MyTrackerPage.js:79 msgid "DMARC Phases" msgstr "Phases DMARC" @@ -788,10 +804,18 @@ msgstr "Statut DMARC" msgid "DMARC Summaries" msgstr "Résumés DMARC" +#: src/summaries/TierTwoSummaries.js:56 +msgid "DMARC Summary" +msgstr "Résumé DMARC" + #: src/summaries/SummaryGroup.js:47 #~ msgid "DMARC phase summary" #~ msgstr "Résumé de la phase DMARC" +#: src/summaries/TierTwoSummaries.js:57 +msgid "DMARC policy of quarantine or reject, and all messages from non-mail domain is rejected" +msgstr "Politique DMARC de mise en quarantaine ou de rejet, et rejet de tous les messages provenant d'un domaine autre que la messagerie." + #: src/dmarc/DmarcReportPage.js:185 #: src/guidance/EmailGuidance.js:272 #~ msgid "DMARC record could not be found during the scan." @@ -963,11 +987,11 @@ msgstr "Domaine:" #: src/app/FloatingMenu.js:116 #: src/domains/DomainsPage.js:82 #: src/domains/DomainsPage.js:116 -#: src/organizationDetails/OrganizationDetails.js:138 +#: src/organizationDetails/OrganizationDetails.js:129 #: src/organizationDetails/OrganizationDomains.js:106 #: src/summaries/Doughnut.js:50 #: src/summaries/Doughnut.js:75 -#: src/user/MyTrackerPage.js:99 +#: src/user/MyTrackerPage.js:82 msgid "Domains" msgstr "Domaines" @@ -1050,6 +1074,10 @@ msgstr "Sécurité du courrier électronique :" msgid "Email Sent" msgstr "Courriel envoyé" +#: src/summaries/TierThreeSummaries.js:17 +msgid "Email Summary" +msgstr "Résumé de l'e-mail" + #: src/user/EditableUserTFAMethod.js:111 msgid "Email Validated" msgstr "Courriel validé" @@ -1319,7 +1347,7 @@ msgstr "Employés du gouvernement du Canada" #~ msgid "Graph direction:" #~ msgstr "Direction du graphique :" -#: src/app/App.js:345 +#: src/app/App.js:350 #: src/dmarc/DmarcReportPage.js:196 #: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" @@ -1399,12 +1427,12 @@ msgstr "HTTPS" msgid "HTTPS (443) Chain" msgstr "Chaîne HTTPS (443)" -#: src/summaries/SummaryGroup.js:16 +#: src/summaries/TierOneSummaries.js:11 msgid "HTTPS Configuration Summary" msgstr "Résumé de la configuration HTTPS" -#: src/organizations/OrganizationCard.js:118 -#: src/organizations/Organizations.js:135 +#: src/organizations/OrganizationCard.js:85 +#: src/organizations/Organizations.js:122 msgid "HTTPS Configured" msgstr "HTTPS configuré" @@ -1425,7 +1453,7 @@ msgstr "Scan HTTPS terminé" msgid "HTTPS Status" msgstr "Statut HTTPS" -#: src/summaries/SummaryGroup.js:17 +#: src/summaries/TierOneSummaries.js:12 msgid "HTTPS is configured and HTTP connections redirect to HTTPS" msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS." @@ -1433,6 +1461,10 @@ msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS. #~ msgid "HTTPS is configured and HTTP connections redirect to HTTPS (ITPIN 6.1.1)" #~ msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS (ITPIN 6.1.1)" +#: src/summaries/TierTwoSummaries.js:40 +msgid "HTTPS is configured, HTTP redirects, and HSTS is enabled" +msgstr "HTTPS est configuré, les redirections HTTP et HSTS sont activés." + #: src/app/RequestScanNotificationHandler.js:90 msgid "HTTPS scan for domain \"{0}\" has completed." msgstr "L'analyse HTTPS du domaine \"{0}\" est terminée." @@ -1590,7 +1622,7 @@ msgstr "Mise en œuvre : <0>Conseils sur la configuration sécurisée des protoc msgid "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" msgstr "Mise en œuvre : <0>Conseils de mise en œuvre : protection du domaine de messagerie (ITSP.40.065 v1.1)" -#: src/summaries/SummaryGroup.js:58 +#: src/summaries/SummaryGroup.js:31 msgid "Implemented" msgstr "Mis en œuvre" @@ -1598,6 +1630,11 @@ msgstr "Mis en œuvre" msgid "Inactive" msgstr "Inactif" +#: src/summaries/TieredSummaries.js:55 +#: src/summaries/TieredSummaries.js:57 +msgid "Include hidden domains in summaries." +msgstr "Inclure les domaines cachés dans les résumés." + #: src/auth/TwoFactorAuthenticatePage.js:81 msgid "Incorrect authenticate.result typename." msgstr "Incorrect authenticate.result typename." @@ -2070,8 +2107,8 @@ msgid "No DKIM selectors are currently attached to this domain. Please contact a msgstr "Aucun sélecteur DKIM n'est actuellement associé à ce domaine. Veuillez contacter un administrateur d'une organisation affiliée pour ajouter des sélecteurs." #: src/summaries/SummaryGroup.js:67 -msgid "No DMARC phase information available for this organization." -msgstr "Aucune information sur la phase DMARC n'est disponible pour cette organisation." +#~ msgid "No DMARC phase information available for this organization." +#~ msgstr "Aucune information sur la phase DMARC n'est disponible pour cette organisation." #: src/admin/AdminDomains.js:156 #: src/domains/DomainsPage.js:89 @@ -2080,11 +2117,11 @@ msgid "No Domains" msgstr "Aucun domaine" #: src/summaries/SummaryGroup.js:37 -msgid "No HTTPS configuration information available for this organization." -msgstr "Aucune information de configuration HTTPS disponible pour cette organisation." +#~ msgid "No HTTPS configuration information available for this organization." +#~ msgstr "Aucune information de configuration HTTPS disponible pour cette organisation." #: src/admin/WebCheckPage.js:94 -#: src/organizations/Organizations.js:78 +#: src/organizations/Organizations.js:81 msgid "No Organizations" msgstr "Aucune organisation" @@ -2162,7 +2199,7 @@ msgstr "Aucun utilisateur" msgid "No values were supplied when attempting to update organization details." msgstr "Aucune valeur n'a été fournie lors de la tentative de mise à jour des détails de l'organisation." -#: src/summaries/SummaryGroup.js:20 +#: src/summaries/SummaryGroup.js:16 msgid "Non-compliant" msgstr "Non conforme" @@ -2179,7 +2216,7 @@ msgstr "Pas après :" msgid "Not Before:" msgstr "Pas avant :" -#: src/summaries/SummaryGroup.js:50 +#: src/summaries/SummaryGroup.js:27 msgid "Not Implemented" msgstr "Non mis en œuvre" @@ -2250,7 +2287,7 @@ msgstr "Vous pouvez notamment communiquer avec l’<0>équipe responsable des se msgid "Organization" msgstr "Organisation" -#: src/organizationDetails/OrganizationDetails.js:67 +#: src/organizationDetails/OrganizationDetails.js:58 msgid "Organization Details" msgstr "Détails de l'organisation" @@ -2493,7 +2530,7 @@ msgstr "Protéger les domaines qui n'envoient pas de courrier électronique - GO #: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 #: src/organizationDetails/OrganizationDomains.js:275 -#: src/organizationDetails/OrganizationDomains.js:314 +#: src/organizationDetails/OrganizationDomains.js:315 msgid "Protocols" msgstr "Protocoles" @@ -2631,7 +2668,7 @@ msgstr "Exigences : <0>Configuration requise pour les services de gestion du cou msgid "Requirements: <0>Web Sites and Services Management Configuration Requirements" msgstr "Exigences : <0>Exigences de configuration de la gestion des sites et services web" -#: src/app/App.js:182 +#: src/app/App.js:187 msgid "Reset Password" msgstr "Réinitialiser le mot de passe" @@ -2723,10 +2760,18 @@ msgstr "Résultats du SPF" msgid "SPF Status" msgstr "Statut SPF" +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF Summary" +msgstr "Résumé du SPF" + #: src/guidance/EmailGuidance.js:167 #~ msgid "SPF record could not be found during the scan." #~ msgstr "L'enregistrement SPF n'a pas pu être trouvé pendant l'analyse." +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF record is deployed and valid" +msgstr "L'enregistrement SPF est déployé et valide" + #: src/app/RequestScanNotificationHandler.js:72 #~ msgid "SSL Scan Complete" #~ msgstr "Analyse SSL terminée" @@ -2887,7 +2932,7 @@ msgstr "En série :" msgid "Services" msgstr "Services" -#: src/organizations/OrganizationCard.js:107 +#: src/organizations/OrganizationCard.js:79 msgid "Services: {domainCount}" msgstr "Services: {domainCount}" @@ -3152,8 +3197,8 @@ msgstr "Soumettre" msgid "Successfully removed user {0}." msgstr "L'utilisateur {0} a été supprimé." -#: src/organizationDetails/OrganizationDetails.js:132 -#: src/user/MyTrackerPage.js:93 +#: src/organizationDetails/OrganizationDetails.js:123 +#: src/user/MyTrackerPage.js:76 msgid "Summary" msgstr "Résumé" @@ -3201,6 +3246,10 @@ msgstr "Résultats TLS" msgid "TLS Scan Complete" msgstr "Scan TLS terminé" +#: src/summaries/TierTwoSummaries.js:45 +msgid "TLS Summary" +msgstr "Résumé TLS" + #: src/app/RequestScanNotificationHandler.js:73 msgid "TLS scan for domain \"{0}\" has completed." msgstr "Le scan TLS pour le domaine \"{0}\" est terminé." @@ -3375,6 +3424,18 @@ msgstr "Ce service n'est pas un service d'hébergement Web et ne nécessite pas msgid "This user is not affiliated with any organizations" msgstr "Cet utilisateur n'est pas affilié à une quelconque organisation" +#: src/summaries/TieredSummaries.js:48 +msgid "Tier 1: Minimum Requirements" +msgstr "Niveau 1 : Exigences minimales" + +#: src/summaries/TieredSummaries.js:75 +msgid "Tier 2: Improved Posture" +msgstr "Niveau 2 : Amélioration de la posture" + +#: src/summaries/TieredSummaries.js:92 +msgid "Tier 3: Compliance" +msgstr "Niveau 3 : Conformité" + #: src/admin/AuditLogTable.js:70 msgid "Time Generated" msgstr "Temps généré" @@ -3684,8 +3745,8 @@ msgid "User:" msgstr "Utilisateur:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:24 -#: src/organizationDetails/OrganizationDetails.js:142 +#: src/admin/AdminPanel.js:31 +#: src/organizationDetails/OrganizationDetails.js:133 msgid "Users" msgstr "Utilisateurs" @@ -3727,7 +3788,7 @@ msgstr "Vérifier le compte" #~ msgid "Vertical View" #~ msgstr "Vue verticale" -#: src/organizations/OrganizationCard.js:144 +#: src/organizations/OrganizationCard.js:101 msgid "View Details" msgstr "Voir les détails" @@ -3791,6 +3852,10 @@ msgstr "Web (HTTPS/TLS)" msgid "Web Check" msgstr "Vérification du Web" +#: src/summaries/TierTwoSummaries.js:39 +msgid "Web Connections Summary" +msgstr "Résumé des connexions web" + #: src/domains/ScanDomain.js:242 #: src/guidance/GuidancePage.js:102 msgid "Web Guidance" @@ -3808,6 +3873,10 @@ msgstr "Sécurité du Web :" #~ msgid "Web Sites and Services Management Configuration Requirements Compliant" #~ msgstr "Gestion des sites et services Web - Exigences de configuration conformes" +#: src/summaries/TierThreeSummaries.js:11 +msgid "Web Summary" +msgstr "Résumé du site web" + #: src/summaries/Doughnut.js:34 msgid "Web-hosting" msgstr "d'hébergement web" @@ -3816,7 +3885,7 @@ msgstr "d'hébergement web" msgid "Welcome to Tracker, please enter your details." msgstr "Bienvenue sur Suivi, veuillez entrer vos coordonnées." -#: src/user/MyTrackerPage.js:78 +#: src/user/MyTrackerPage.js:63 msgid "Welcome to your personal view of Tracker. Moderate the security posture of domains of interest across multiple organizations. To add domains to this view, use the star icon buttons available on domain lists." msgstr "Bienvenue dans votre vision personnelle de Suivi. Modérez la posture de sécurité des domaines d'intérêt à travers plusieurs organisations. Pour ajouter des domaines à cette vue, utilisez les boutons de l'icône étoile disponibles sur les listes de domaines." @@ -4010,10 +4079,10 @@ msgstr "contactez-nous" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:100 -#: src/app/App.js:279 -#: src/user/MyTrackerPage.js:43 -#: src/user/MyTrackerPage.js:74 +#: src/app/App.js:105 +#: src/app/App.js:284 +#: src/user/MyTrackerPage.js:33 +#: src/user/MyTrackerPage.js:59 msgid "myTracker" msgstr "monSuivi" diff --git a/frontend/src/organizationDetails/OrganizationDetails.js b/frontend/src/organizationDetails/OrganizationDetails.js index 4f23d46a91..9dc7ff66b8 100644 --- a/frontend/src/organizationDetails/OrganizationDetails.js +++ b/frontend/src/organizationDetails/OrganizationDetails.js @@ -22,7 +22,7 @@ import { ErrorBoundary } from 'react-error-boundary' import { OrganizationDomains } from './OrganizationDomains' import { OrganizationAffiliations } from './OrganizationAffiliations' -import { OrganizationSummary } from './OrganizationSummary' +import { TieredSummaries } from '../summaries/TieredSummaries' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { LoadingMessage } from '../components/LoadingMessage' @@ -152,7 +152,7 @@ export default function OrganizationDetails() { - + diff --git a/frontend/src/organizationDetails/OrganizationSummary.js b/frontend/src/organizationDetails/OrganizationSummary.js deleted file mode 100644 index 133c47da12..0000000000 --- a/frontend/src/organizationDetails/OrganizationSummary.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { Box } from '@chakra-ui/react' -import { SummaryGroup } from '../summaries/SummaryGroup' -import { number, object, string } from 'prop-types' - -export function OrganizationSummary({ summaries }) { - return ( - - - - ) -} - -OrganizationSummary.propTypes = { - summaries: object, - domainCount: number, - userCount: number, - city: string, - province: string, -} diff --git a/frontend/src/organizations/OrganizationCard.js b/frontend/src/organizations/OrganizationCard.js index db6a2738e6..7b81c30827 100644 --- a/frontend/src/organizations/OrganizationCard.js +++ b/frontend/src/organizations/OrganizationCard.js @@ -1,28 +1,11 @@ import React from 'react' -import { - Box, - Button, - Flex, - ListItem, - Progress, - Stack, - Text, - useBreakpointValue, -} from '@chakra-ui/react' +import { Box, Button, Flex, ListItem, Progress, Stack, Text, useBreakpointValue } from '@chakra-ui/react' import { CheckCircleIcon } from '@chakra-ui/icons' import { Link as RouteLink, useRouteMatch } from 'react-router-dom' import { bool, number, object, string } from 'prop-types' import { Trans } from '@lingui/macro' -export function OrganizationCard({ - name, - acronym, - slug, - domainCount, - verified, - summaries, - ...rest -}) { +export function OrganizationCard({ name, acronym, slug, domainCount, verified, summaries, ...rest }) { const { path, _url } = useRouteMatch() let httpsValue = 0 let dmarcValue = 0 @@ -75,24 +58,13 @@ export function OrganizationCard({ maxWidth="100%" > - + {name} ({acronym}) - {verified && ( - - )} + {verified && } - + HTTPS Configured @@ -121,11 +88,7 @@ export function OrganizationCard({ - + DMARC Configured @@ -133,13 +96,7 @@ export function OrganizationCard({ {hasButton && ( -