mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-05 23:53:21 +00:00
[PM-26672] Add Google Workspace integration tests to CI pipeline (#909)
- reorganize integration test files to allow for future additions - add Google Workspace integration tests to the Github workflow - refactor to run tests selective based on changed files and use Azure Key Vault
This commit is contained in:
99
.github/workflows/integration-test.yml
vendored
99
.github/workflows/integration-test.yml
vendored
@@ -2,25 +2,36 @@ name: Integration Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Integration tests are slow, so only run them if relevant files have changed.
|
||||
# This is done at the workflow level and at the job level.
|
||||
# Make sure these triggers stay consistent with the 'changed-files' job.
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- 'main'
|
||||
- 'rc'
|
||||
paths:
|
||||
- ".github/workflows/integration-test.yml" # this file
|
||||
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
|
||||
- "./utils/**/*" # any change to test fixtures
|
||||
- "./docker-compose.yml" # any change to Docker configuration
|
||||
- "./package.json" # dependencies
|
||||
- "docker-compose.yml" # any change to Docker configuration
|
||||
- "package.json" # dependencies
|
||||
- "utils/**" # any change to test fixtures
|
||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
# Add directory services here as we add test coverage
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/integration-test.yml" # this file
|
||||
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
|
||||
- "./utils/**/*" # any change to test fixtures
|
||||
- "./docker-compose.yml" # any change to Docker configuration
|
||||
- "./package.json" # dependencies
|
||||
- "docker-compose.yml" # any change to Docker configuration
|
||||
- "package.json" # dependencies
|
||||
- "utils/**" # any change to test fixtures
|
||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
# Add directory services here as we add test coverage
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # required by dorny/test-reporter to upload its results
|
||||
id-token: write # required to use OIDC to login to Azure Key Vault
|
||||
jobs:
|
||||
testing:
|
||||
name: Run tests
|
||||
@@ -50,23 +61,79 @@ jobs:
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install mkcert
|
||||
# Get secrets from Azure Key Vault
|
||||
- name: Azure Login
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get KV Secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-directory-connector
|
||||
secrets: "GOOGLE-ADMIN-USER,GOOGLE-CLIENT-EMAIL,GOOGLE-DOMAIN,GOOGLE-PRIVATE-KEY"
|
||||
|
||||
- name: Azure Logout
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
# Only run relevant tests depending on what files have changed.
|
||||
# This should be kept consistent with the workflow level triggers.
|
||||
# Note: docker-compose.yml is only used for ldap for now
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
with:
|
||||
list-files: shell
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Add directory services here as we add test coverage
|
||||
filters: |
|
||||
common:
|
||||
- '.github/workflows/integration-test.yml'
|
||||
- 'utils/**'
|
||||
- 'package.json'
|
||||
- 'src/services/sync.service.ts'
|
||||
ldap:
|
||||
- 'docker-compose.yml'
|
||||
- 'src/services/directory-services/ldap-directory.service*'
|
||||
google:
|
||||
- 'src/services/directory-services/gsuite-directory.service*'
|
||||
|
||||
# LDAP
|
||||
- name: Setup LDAP integration tests
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install mkcert
|
||||
|
||||
- name: Setup LDAP integration tests
|
||||
run: npm run test:integration:setup
|
||||
npm run test:integration:setup
|
||||
|
||||
- name: Run LDAP integration tests
|
||||
run: npx jest ldap-directory.service.integration.spec.ts --coverage
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||
env:
|
||||
JEST_JUNIT_UNIQUE_OUTPUT_NAME: "true" # avoids junit outputs from clashing
|
||||
run: npx jest ldap-directory.service.integration.spec.ts --coverage --coverageDirectory=coverage-ldap
|
||||
|
||||
# Google Workspace
|
||||
- name: Run Google Workspace integration tests
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.google == 'true'
|
||||
env:
|
||||
GOOGLE_DOMAIN: ${{ steps.get-kv-secrets.outputs.GOOGLE-DOMAIN }}
|
||||
GOOGLE_ADMIN_USER: ${{ steps.get-kv-secrets.outputs.GOOGLE-ADMIN-USER }}
|
||||
GOOGLE_CLIENT_EMAIL: ${{ steps.get-kv-secrets.outputs.GOOGLE-CLIENT-EMAIL }}
|
||||
GOOGLE_PRIVATE_KEY: ${{ steps.get-kv-secrets.outputs.GOOGLE-PRIVATE-KEY }}
|
||||
JEST_JUNIT_UNIQUE_OUTPUT_NAME: "true" # avoids junit outputs from clashing
|
||||
run: |
|
||||
npx jest gsuite-directory.service.integration.spec.ts --coverage --coverageDirectory=coverage-google
|
||||
|
||||
- name: Report test results
|
||||
id: report
|
||||
uses: dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3 # v2.1.1
|
||||
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository && !cancelled()
|
||||
with:
|
||||
name: Test Results
|
||||
path: "junit.xml"
|
||||
path: "junit.xml*"
|
||||
reporter: jest-junit
|
||||
fail-on-error: true
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -33,8 +33,8 @@ build-cli
|
||||
.angular/cache
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
junit.xml
|
||||
coverage*
|
||||
junit.xml*
|
||||
|
||||
# Misc
|
||||
*.crx
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { IDirectoryService } from "@/src/services/directory.service";
|
||||
import { IDirectoryService } from "@/src/services/directory-services/directory.service";
|
||||
|
||||
export abstract class DirectoryFactoryService {
|
||||
abstract createService(type: DirectoryType): IDirectoryService;
|
||||
|
||||
@@ -768,5 +768,8 @@
|
||||
},
|
||||
"launchWebVault": {
|
||||
"message": "Launch Web Vault"
|
||||
},
|
||||
"authenticationFailed": {
|
||||
"message": "Authentication failed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import { DirectoryFactoryService } from "../abstractions/directory-factory.servi
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
import { EntraIdDirectoryService } from "./entra-id-directory.service";
|
||||
import { GSuiteDirectoryService } from "./gsuite-directory.service";
|
||||
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||
import { OktaDirectoryService } from "./okta-directory.service";
|
||||
import { OneLoginDirectoryService } from "./onelogin-directory.service";
|
||||
import { EntraIdDirectoryService } from "./directory-services/entra-id-directory.service";
|
||||
import { GSuiteDirectoryService } from "./directory-services/gsuite-directory.service";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
import { OktaDirectoryService } from "./directory-services/okta-directory.service";
|
||||
import { OneLoginDirectoryService } from "./directory-services/onelogin-directory.service";
|
||||
|
||||
export class DefaultDirectoryFactoryService implements DirectoryFactoryService {
|
||||
constructor(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { UserEntry } from "../../models/userEntry";
|
||||
|
||||
export interface IDirectoryService {
|
||||
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
|
||||
@@ -7,14 +7,14 @@ import * as graphType from "@microsoft/microsoft-graph-types";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { EntraIdConfiguration } from "../models/entraIdConfiguration";
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { EntraIdConfiguration } from "../../models/entraIdConfiguration";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { SyncConfiguration } from "../../models/syncConfiguration";
|
||||
import { UserEntry } from "../../models/userEntry";
|
||||
import { BaseDirectoryService } from "../baseDirectory.service";
|
||||
|
||||
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||
import { IDirectoryService } from "./directory.service";
|
||||
|
||||
const EntraIdPublicIdentityAuthority = "login.microsoftonline.com";
|
||||
@@ -1,18 +1,18 @@
|
||||
import { config as dotenvConfig } from "dotenv";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../jslib/common/src/abstractions/log.service";
|
||||
import { I18nService } from "../../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../../jslib/common/src/abstractions/log.service";
|
||||
import {
|
||||
getGSuiteConfiguration,
|
||||
getSyncConfiguration,
|
||||
} from "../../utils/google-workspace/config-fixtures";
|
||||
import { groupFixtures } from "../../utils/google-workspace/group-fixtures";
|
||||
import { userFixtures } from "../../utils/google-workspace/user-fixtures";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
} from "../../../utils/google-workspace/config-fixtures";
|
||||
import { groupFixtures } from "../../../utils/google-workspace/group-fixtures";
|
||||
import { userFixtures } from "../../../utils/google-workspace/user-fixtures";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { StateService } from "../state.service";
|
||||
|
||||
import { GSuiteDirectoryService } from "./gsuite-directory.service";
|
||||
import { StateService } from "./state.service";
|
||||
|
||||
// These tests integrate with a test Google Workspace instance.
|
||||
// Credentials are located in the shared Bitwarden collection for Directory Connector testing.
|
||||
@@ -24,10 +24,13 @@ dotenvConfig({ path: "utils/.env" });
|
||||
// These filters target integration test data.
|
||||
// These should return data that matches the user and group fixtures exactly.
|
||||
// There may be additional data present if not used.
|
||||
const INTEGRATION_USER_FILTER =
|
||||
"exclude:integration-user-a@bwrox.dev|orgUnitPath='/Integration testing'";
|
||||
const INTEGRATION_USER_FILTER = "|orgUnitPath='/Integration testing'";
|
||||
const INTEGRATION_GROUP_FILTER = "|name:Integration*";
|
||||
|
||||
// These tests are slow!
|
||||
// Increase the default timeout from 5s to 15s
|
||||
jest.setTimeout(15000);
|
||||
|
||||
describe("gsuiteDirectoryService", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
@@ -4,14 +4,14 @@ import { admin_directory_v1, google } from "googleapis";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { GSuiteConfiguration } from "../../models/gsuiteConfiguration";
|
||||
import { SyncConfiguration } from "../../models/syncConfiguration";
|
||||
import { UserEntry } from "../../models/userEntry";
|
||||
import { BaseDirectoryService } from "../baseDirectory.service";
|
||||
|
||||
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||
import { IDirectoryService } from "./directory.service";
|
||||
|
||||
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||
@@ -253,7 +253,15 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
await this.client.authorize();
|
||||
} catch (error) {
|
||||
// Catch and rethrow this to sanitize any sensitive info (e.g. private key) in the error message
|
||||
this.logService.error(
|
||||
`Google Workspace authentication failed: ${error?.name || "Unknown error"}`,
|
||||
);
|
||||
throw new Error(this.i18nService.t("authenticationFailed"));
|
||||
}
|
||||
|
||||
this.authParams = {
|
||||
auth: this.client,
|
||||
@@ -1,14 +1,17 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../jslib/common/src/abstractions/log.service";
|
||||
import { getLdapConfiguration, getSyncConfiguration } from "../../utils/openldap/config-fixtures";
|
||||
import { groupFixtures } from "../../utils/openldap/group-fixtures";
|
||||
import { userFixtures } from "../../utils/openldap/user-fixtures";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { I18nService } from "../../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../../jslib/common/src/abstractions/log.service";
|
||||
import {
|
||||
getLdapConfiguration,
|
||||
getSyncConfiguration,
|
||||
} from "../../../utils/openldap/config-fixtures";
|
||||
import { groupFixtures } from "../../../utils/openldap/group-fixtures";
|
||||
import { userFixtures } from "../../../utils/openldap/user-fixtures";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { StateService } from "../state.service";
|
||||
|
||||
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||
import { StateService } from "./state.service";
|
||||
|
||||
// These tests integrate with the OpenLDAP docker image and seed data located in the openldap folder.
|
||||
// To run theses tests:
|
||||
@@ -7,12 +7,12 @@ import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { LdapConfiguration } from "../models/ldapConfiguration";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { LdapConfiguration } from "../../models/ldapConfiguration";
|
||||
import { SyncConfiguration } from "../../models/syncConfiguration";
|
||||
import { UserEntry } from "../../models/userEntry";
|
||||
|
||||
import { IDirectoryService } from "./directory.service";
|
||||
|
||||
@@ -3,14 +3,14 @@ import * as https from "https";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { OktaConfiguration } from "../models/oktaConfiguration";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { OktaConfiguration } from "../../models/oktaConfiguration";
|
||||
import { SyncConfiguration } from "../../models/syncConfiguration";
|
||||
import { UserEntry } from "../../models/userEntry";
|
||||
import { BaseDirectoryService } from "../baseDirectory.service";
|
||||
|
||||
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||
import { IDirectoryService } from "./directory.service";
|
||||
|
||||
const DelayBetweenBuildGroupCallsInMilliseconds = 500;
|
||||
@@ -1,14 +1,14 @@
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { OneLoginConfiguration } from "../models/oneLoginConfiguration";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { OneLoginConfiguration } from "../../models/oneLoginConfiguration";
|
||||
import { SyncConfiguration } from "../../models/syncConfiguration";
|
||||
import { UserEntry } from "../../models/userEntry";
|
||||
import { BaseDirectoryService } from "../baseDirectory.service";
|
||||
|
||||
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||
import { IDirectoryService } from "./directory.service";
|
||||
|
||||
// Basic email validation: something@something.something
|
||||
@@ -12,7 +12,7 @@ import { DirectoryFactoryService } from "../abstractions/directory-factory.servi
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
import { SingleRequestBuilder } from "./single-request-builder";
|
||||
import { StateService } from "./state.service";
|
||||
import { SyncService } from "./sync.service";
|
||||
|
||||
@@ -11,8 +11,8 @@ import { DirectoryFactoryService } from "../abstractions/directory-factory.servi
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
import { I18nService } from "./i18n.service";
|
||||
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||
import { SingleRequestBuilder } from "./single-request-builder";
|
||||
import { StateService } from "./state.service";
|
||||
import { SyncService } from "./sync.service";
|
||||
|
||||
Reference in New Issue
Block a user