Merge pull request #12490 from afscrome/aadazurecli

Use Azure Identity to Authenticate with Microsoft Graph API
This commit is contained in:
Patrik Oldsberg
2022-07-13 10:26:13 +02:00
committed by GitHub
11 changed files with 405 additions and 110 deletions
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/plugin-catalog-backend-module-msgraph': minor
---
Microsoft Graph plugin can supports many more options for authenticating with the Microsoft Graph API.
Previously only ClientId/ClientSecret was supported, but now all the authentication options of `DefaultAzureCredential` from `@azure/identity` are supported.
Including Managed Identity, Client Certificate, Azure CLI and VS Code.
If `clientId` and `clientSecret` are specified in configuration, the plugin behaves the same way as before.
If these fields are omitted, the plugin uses `DefaultAzureCredential` to automatically determine the best authentication method.
This is particularly useful for local development environments - the default configuration will try to use existing credentials from Visual Studio Code, Azure CLI and Azure PowerShell, without the user needing to configure any credentials in app-config.yaml
+230 -3
View File
@@ -3,12 +3,239 @@ id: org
title: Microsoft Azure Active Directory Organizational Data
sidebar_label: Org Data
# prettier-ignore
description: Importing users and groups from a Microsoft Azure Active Directory into Backstage
description: Importing users and groups from Microsoft Azure Active Directory into Backstage
---
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from a tenant in Microsoft Azure Active Directory via the
Microsoft Graph API.
More details on this are available in the
[README of the `@backstage/plugin-catalog-backend-module-msgraph` package](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md).
## Installation
The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package.
```bash
# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-msgraph
```
Next add the basic configuration to `app-config.yaml`
```yaml
catalog:
providers:
microsoftGraphOrg:
default:
tenantId: ${AZURE_TENANT_ID}
user:
filter: accountEnabled eq true and userType eq 'member'
group:
filter: >
securityEnabled eq false
and mailEnabled eq true
and groupTypes/any(c:c+eq+'Unified')
```
Finally, register the plugin in `catalog.ts`.
For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts.
```diff
// packages/backend/src/plugins/catalog.ts
+import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
+ builder.addEntityProvider(
+ MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
+ logger: env.logger,
+ schedule: env.scheduler.createScheduledTaskRunner({
+ frequency: { hours: 1 },
+ timeout: { minutes: 50 },
+ initialDelay: { seconds: 15}
+ }),
+ }),
+ );
```
## Authenticating with Microsoft Graph
### Local Development
For a local dev environment, it's recommended you have the Azure CLI or Azure PowerShell installed, and are logged in to those.
Alternatively you can use VSCode with the Azure extension if you install `@azure/identity-vscode`.
When these are set up, the plugin will authenticate with the Microsoft Graph API without you needing to configure any credentials, or granting any special permissions.
If you can't do this, you'll have to create an App Registration.
## App Registration
If none of the other authentication methods work, you can create an app registration in the azure portal.
By default the graph plugin requires the following Application permissions (not Delegated) for Microsoft Graph:
- `GroupMember.Read.All`
- `User.Read.All`
If your organization required Admin Consent for these permissions, that will need to be granted.
When authenticating with a ClientId/ClientSecret, you can either set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_SECRET` environment variables, or specify the values in configuration
```yaml
microsoftGraphOrg:
default:
##...
clientId: 9ef1aac6-b454-4e69-9cf5-7199df049281
clientSecret: REDACTED
```
To authenticate with a certificate rather than a client secret, you can set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_CERTIFICATE_PATH` environments
### Managed Identity
If deploying to resources that supports Managed Identity, and has identities configured (e.g. Azure App Services, Azure Container Apps), Managed Identity should be picked up without any additional configuration.
If your app has multiple managed identities, you may need to set the `AZURE_CLIENT_ID` environment variable to tell Azure Identity which identity to use.
You will need to grant the same permissions to your identity as described for App Registrations above.
## Filtering imported Users and Groups
By default, the plugin will import all users and groups from your directory.
This can be customized through filters and search queries.
### Groups
A smaller set of groups can be obtained by configuring a search query or a filter.
If both `filter` and `search` are provided, then groups must match both to be ingested.
```yaml
microsoftGraphOrg:
providerId:
group:
filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified')
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
In addition to these groups, one additional group will be created for your organization.
All imported groups will be a child of this group.
### Users
There are two modes for importing users - You can import all user objects matching a `filter`.
```yaml
microsoftGraphOrg:
providerId:
user:
filter: accountEnabled eq true and userType eq 'member'
```
Alternatively you can import users that are members of specific groups.
For each group matching the `search` and `filter` query, each group member will be imported.
Only direct group members will be imported, not transient users.
```yaml
microsoftGraphOrg:
providerId:
userGroupMember:
filter: "displayName eq 'Backstage Users'"
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
## Customizing Transformation
Ingested entities can be customized by providing custom transformers.
These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer`
Entities can also be excluded from backstage by returning `undefined`.
These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider`
```diff
builder.addEntityProvider(
MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
// ...
+ groupTransformer: myGroupTransformer,
+ userTransformer: myUserTransformer,
+ organizationTransformer: myOrganizationTransformer,
}),
);
```
When using custom transformers, you may want to customize the data returned.
Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need
```yaml
microsoftGraphOrg:
providerId:
user:
expand: manager
group:
expand: member
select: ['id', 'displayName', 'description']
```
The following provides an example of each kind of transformer
```ts
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
import {
defaultGroupTransformer,
defaultUserTransformer,
defaultOrganizationTransformer,
} from '@backstage/plugin-catalog-backend-module-msgraph';
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
// This group transformer completely replaces the built in logic with custom logic.
export async function myGroupTransformer(
group: MicrosoftGraph.Group,
groupPhoto?: string,
): Promise<GroupEntity | undefined> {
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Group',
metadata: {
name: group.id!,
annotations: {},
},
spec: {
type: 'aad',
children: [],
},
};
}
// This user transformer makes use of the built in logic, but also sets the description field
export async function myUserTransformer(
graphUser: MicrosoftGraph.User,
userPhoto?: string,
): Promise<UserEntity | undefined> {
const backstageUser = await defaultUserTransformer(graphUser, userPhoto);
if (backstageUser) {
backstageUser.metadata.description = 'Loaded from Azure Active Directory';
}
return backstageUser;
}
// Example organization transformer that removes the organization group completely
export async function myOrganizationTransformer(
graphOrganization: MicrosoftGraph.Organization,
): Promise<GroupEntity | undefined> {
return undefined;
}
```
## Troubleshooting
### Authentication / Token Errors
See [Troubleshooting Azure Identity Authentication Issues](https://aka.ms/azsdk/js/identity/troubleshoot)
### Error while reading users from Microsoft Graph: Authorization_RequestDenied - Insufficient privileges to complete the operation.
- Make sure you've granted all the required permissions to your application registration or managed identity
- Make sure the permissions are `Application` permissions rather than `Delegated`
- If your organization has configured "Admin consent" to be required, make sure this has been granted for your application permissions
- If your group queries are returning Microsoft Teams groups, you may need to grant addition permissions (e.g. `Team.ReadBasic.All`, `TeamMember.Read.All`)
- If you've added additional `select` or `expand` fields, those may need additional permissions granted
@@ -6,13 +6,18 @@ This provider is useful if you want to import users and groups from Azure Active
## Getting Started
1. Create or use an existing App registration in the [Microsoft Azure Portal](https://portal.azure.com/).
The App registration requires at least the API permissions `Group.Read.All`,
`GroupMember.Read.All`, `User.Read` and `User.Read.All` for Microsoft Graph
(if you still run into errors about insufficient privileges, add
`Team.ReadBasic.All` and `TeamMember.Read.All` too).
1. Choose your authentication method - all methods supported by [DefaultAzureCredential](https://docs.microsoft.com/en-us/javascript/api/overview/azure/identity-readme?view=azure-node-latest#defaultazurecredential)
2. Configure the entity provider:
- For local dev, use Azure CLI, Azure PowerShell or Visual Studio Code for authentication
- If your infrastructure supports Managed Identity, use that
- Otherwise use an App Registration
1. If using Managed Identity or App Registration for authentication, grant the following application permissions (not delegated)
- `GroupMember.Read.All`
- `User.Read.All`
1. Configure the entity provider:
```yaml
# app-config.yaml
@@ -24,11 +29,13 @@ catalog:
authority: https://login.microsoftonline.com
# If you don't know you tenantId, you can use Microsoft Graph Explorer
# to query it
tenantId: ${MICROSOFT_GRAPH_TENANT_ID}
tenantId: ${AZURE_TENANT_ID}
# Optional ClientId and ClientSecret if you don't want to use `DefaultAzureCredential`
# for authentication
# Client Id and Secret can be created under Certificates & secrets in
# the App registration in the Microsoft Azure Portal.
clientId: ${MICROSOFT_GRAPH_CLIENT_ID}
clientSecret: ${MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN}
clientId: ${AZURE_CLIENT_ID}
clientSecret: ${AZURE_CLIENT_SECRET}
# Optional mode for querying which defaults to "basic".
# By default, the Microsoft Graph API only provides the basic feature set
# for querying. Certain features are limited to advanced querying capabilities.
@@ -108,8 +115,9 @@ yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-msgraph
+ MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
+ logger: env.logger,
+ schedule: env.scheduler.createScheduledTaskRunner({
+ frequency: { minutes: 5 },
+ timeout: { minutes: 3 },
+ frequency: { hours: 1 },
+ timeout: { minutes: 50 },
+ initialDelay: { seconds: 15}
+ }),
+ }),
+ );
@@ -12,9 +12,9 @@ import { GroupEntity } from '@backstage/catalog-model';
import { LocationSpec } from '@backstage/plugin-catalog-backend';
import { Logger } from 'winston';
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
import * as msal from '@azure/msal-node';
import { Response as Response_2 } from 'node-fetch';
import { TaskRunner } from '@backstage/backend-tasks';
import { TokenCredential } from '@azure/identity';
import { UserEntity } from '@backstage/catalog-model';
// @public
@@ -65,7 +65,7 @@ export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id';
// @public
export class MicrosoftGraphClient {
constructor(baseUrl: string, pca: msal.ConfidentialClientApplication);
constructor(baseUrl: string, tokenCredential: TokenCredential);
static create(config: MicrosoftGraphProviderConfig): MicrosoftGraphClient;
getGroupMembers(groupId: string): AsyncIterable<GroupMember>;
// (undocumented)
@@ -190,8 +190,8 @@ export type MicrosoftGraphProviderConfig = {
target: string;
authority?: string;
tenantId: string;
clientId: string;
clientSecret: string;
clientId?: string;
clientSecret?: string;
userFilter?: string;
userExpand?: string;
userGroupMemberFilter?: string;
+4 -4
View File
@@ -50,13 +50,13 @@ export interface Config {
/**
* The OAuth client ID to use for authenticating requests.
*/
clientId: string;
clientId?: string;
/**
* The OAuth client secret to use for authenticating requests.
*
* @visibility secret
*/
clientSecret: string;
clientSecret?: string;
// TODO: Consider not making these config options and pass them in the
// constructor instead. They are probably not environment specific, so
@@ -130,13 +130,13 @@ export interface Config {
/**
* The OAuth client ID to use for authenticating requests.
*/
clientId: string;
clientId?: string;
/**
* The OAuth client secret to use for authenticating requests.
*
* @visibility secret
*/
clientSecret: string;
clientSecret?: string;
user?: {
/**
@@ -33,7 +33,7 @@
"start": "backstage-cli package start"
},
"dependencies": {
"@azure/msal-node": "^1.1.0",
"@azure/identity": "^2.1.0",
"@backstage/backend-tasks": "^0.3.3-next.3",
"@backstage/catalog-model": "^1.1.0-next.3",
"@backstage/config": "^1.0.1",
@@ -43,9 +43,9 @@
"lodash": "^4.17.21",
"node-fetch": "^2.6.7",
"p-limit": "^3.0.2",
"qs": "^6.9.4",
"uuid": "^8.0.0",
"winston": "^3.2.1",
"qs": "^6.9.4"
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/backend-common": "^0.14.1-next.3",
@@ -14,30 +14,26 @@
* limitations under the License.
*/
import * as msal from '@azure/msal-node';
import { TokenCredential } from '@azure/identity';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { MicrosoftGraphClient } from './client';
describe('MicrosoftGraphClient', () => {
const confidentialClientApplication: jest.Mocked<msal.ConfidentialClientApplication> =
{
acquireTokenByClientCredential: jest.fn(),
} as any;
const tokenCredential: jest.Mocked<TokenCredential> = {
getToken: jest.fn(),
} as any;
let client: MicrosoftGraphClient;
const worker = setupServer();
setupRequestMockHandlers(worker);
beforeEach(() => {
confidentialClientApplication.acquireTokenByClientCredential.mockResolvedValue(
{ token: 'ACCESS_TOKEN' } as any,
);
client = new MicrosoftGraphClient(
'https://example.com',
confidentialClientApplication,
);
tokenCredential.getToken.mockResolvedValue({
token: 'ACCESS_TOKEN',
} as any);
client = new MicrosoftGraphClient('https://example.com', tokenCredential);
});
afterEach(() => {
@@ -55,12 +51,10 @@ describe('MicrosoftGraphClient', () => {
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ value: 'example' });
expect(
confidentialClientApplication.acquireTokenByClientCredential,
).toBeCalledTimes(1);
expect(
confidentialClientApplication.acquireTokenByClientCredential,
).toBeCalledWith({ scopes: ['https://graph.microsoft.com/.default'] });
expect(tokenCredential.getToken).toBeCalledTimes(1);
expect(tokenCredential.getToken).toBeCalledWith(
'https://graph.microsoft.com/.default',
);
});
it('should perform simple api request', async () => {
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import * as msal from '@azure/msal-node';
import {
TokenCredential,
DefaultAzureCredential,
ClientSecretCredential,
} from '@azure/identity';
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
import fetch, { Response } from 'node-fetch';
import qs from 'qs';
@@ -77,25 +81,32 @@ export class MicrosoftGraphClient {
* @param config - Configuration for Interacting with Graph API
*/
static create(config: MicrosoftGraphProviderConfig): MicrosoftGraphClient {
const clientConfig: msal.Configuration = {
auth: {
clientId: config.clientId,
clientSecret: config.clientSecret,
authority: `${config.authority}/${config.tenantId}`,
},
const options = {
authorityHost: config.authority,
tenantId: config.tenantId,
};
const pca = new msal.ConfidentialClientApplication(clientConfig);
return new MicrosoftGraphClient(config.target, pca);
const credential =
config.clientId && config.clientSecret
? new ClientSecretCredential(
config.tenantId,
config.clientId,
config.clientSecret,
options,
)
: new DefaultAzureCredential(options);
return new MicrosoftGraphClient(config.target, credential);
}
/**
* @param baseUrl - baseUrl of Graph API {@link MicrosoftGraphProviderConfig.target}
* @param pca - instance of `msal.ConfidentialClientApplication` that is used to acquire token for Graph API calls
* @param tokenCredential - instance of `TokenCredential` that is used to acquire token for Graph API calls
*
*/
constructor(
private readonly baseUrl: string,
private readonly pca: msal.ConfidentialClientApplication,
private readonly tokenCredential: TokenCredential,
) {}
/**
@@ -202,18 +213,18 @@ export class MicrosoftGraphClient {
headers?: Record<string, string>,
): Promise<Response> {
// Make sure that we always have a valid access token (might be cached)
const token = await this.pca.acquireTokenByClientCredential({
scopes: ['https://graph.microsoft.com/.default'],
});
const token = await this.tokenCredential.getToken(
'https://graph.microsoft.com/.default',
);
if (!token) {
throw new Error('Error while requesting token for Microsoft Graph');
throw new Error('Failed to obtain token from Azure Identity');
}
return await fetch(url, {
headers: {
...headers,
Authorization: `Bearer ${token.accessToken}`,
Authorization: `Bearer ${token.token}`,
},
});
}
@@ -25,8 +25,6 @@ describe('readMicrosoftGraphConfig', () => {
id: 'target',
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
},
],
};
@@ -36,11 +34,6 @@ describe('readMicrosoftGraphConfig', () => {
id: 'target',
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.microsoftonline.com',
userFilter: undefined,
groupFilter: undefined,
},
];
expect(actual).toEqual(expected);
@@ -72,7 +65,7 @@ describe('readMicrosoftGraphConfig', () => {
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com',
authority: 'https://login.example.com/',
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
groupExpand: 'member',
@@ -87,12 +80,7 @@ describe('readMicrosoftGraphConfig', () => {
const config = {
providers: [
{
id: 'target',
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com/',
userFilter: 'accountEnabled eq true',
userGroupMemberFilter: 'any',
},
@@ -105,12 +93,7 @@ describe('readMicrosoftGraphConfig', () => {
const config = {
providers: [
{
id: 'target',
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com/',
userFilter: 'accountEnabled eq true',
userGroupMemberSearch: 'any',
},
@@ -118,6 +101,30 @@ describe('readMicrosoftGraphConfig', () => {
};
expect(() => readMicrosoftGraphConfig(new ConfigReader(config))).toThrow();
});
it('should fail if clientId is set without clientSecret', () => {
const config = {
providers: [
{
tenantId: 'tenantId',
clientId: 'clientId',
},
],
};
expect(() => readMicrosoftGraphConfig(new ConfigReader(config))).toThrow();
});
it('should fail if clientSecret is set without clientId', () => {
const config = {
providers: [
{
tenantId: 'tenantId',
clientSecret: 'clientId',
},
],
};
expect(() => readMicrosoftGraphConfig(new ConfigReader(config))).toThrow();
});
});
describe('readProviderConfigs', () => {
@@ -127,10 +134,7 @@ describe('readProviderConfigs', () => {
providers: {
microsoftGraphOrg: {
customProviderId: {
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
},
},
},
@@ -140,11 +144,8 @@ describe('readProviderConfigs', () => {
const expected = [
{
id: 'customProviderId',
target: 'target',
target: 'https://graph.microsoft.com/v1.0',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.microsoftonline.com',
},
];
expect(actual).toEqual(expected);
@@ -183,7 +184,7 @@ describe('readProviderConfigs', () => {
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com',
authority: 'https://login.example.com/',
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
groupExpand: 'member',
@@ -200,11 +201,7 @@ describe('readProviderConfigs', () => {
providers: {
microsoftGraphOrg: {
customProviderId: {
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com/',
user: {
filter: 'accountEnabled eq true',
},
@@ -225,11 +222,7 @@ describe('readProviderConfigs', () => {
providers: {
microsoftGraphOrg: {
customProviderId: {
target: 'target',
tenantId: 'tenantId',
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com/',
user: {
filter: 'accountEnabled eq true',
},
@@ -243,4 +236,36 @@ describe('readProviderConfigs', () => {
};
expect(() => readProviderConfigs(new ConfigReader(config))).toThrow();
});
it('should fail if clientId is set without clientSecret', () => {
const config = {
catalog: {
providers: {
microsoftGraphOrg: {
customProviderId: {
tenantId: 'tenantId',
clientId: 'id',
},
},
},
},
};
expect(() => readProviderConfigs(new ConfigReader(config))).toThrow();
});
it('should fail if clientSecret is set without clientId', () => {
const config = {
catalog: {
providers: {
microsoftGraphOrg: {
customProviderId: {
tenantId: 'tenantId',
clientSecret: 'clientSecret',
},
},
},
},
};
expect(() => readProviderConfigs(new ConfigReader(config))).toThrow();
});
});
@@ -17,7 +17,6 @@
import { Config } from '@backstage/config';
import { trimEnd } from 'lodash';
const DEFAULT_AUTHORITY = 'https://login.microsoftonline.com';
const DEFAULT_PROVIDER_ID = 'default';
const DEFAULT_TARGET = 'https://graph.microsoft.com/v1.0';
@@ -49,12 +48,14 @@ export type MicrosoftGraphProviderConfig = {
tenantId: string;
/**
* The OAuth client ID to use for authenticating requests.
* If specified, ClientSecret must also be specified
*/
clientId: string;
clientId?: string;
/**
* The OAuth client secret to use for authenticating requests.
* If specified, ClientId must also be specified
*/
clientSecret: string;
clientSecret?: string;
/**
* The filter to apply to extract users.
*
@@ -131,14 +132,15 @@ export function readMicrosoftGraphConfig(
const providerConfigs = config.getOptionalConfigArray('providers') ?? [];
for (const providerConfig of providerConfigs) {
const target = trimEnd(providerConfig.getString('target'), '/');
const target = trimEnd(
providerConfig.getOptionalString('target') ?? DEFAULT_TARGET,
'/',
);
const authority = providerConfig.getOptionalString('authority');
const authority = providerConfig.getOptionalString('authority')
? trimEnd(providerConfig.getOptionalString('authority'), '/')
: DEFAULT_AUTHORITY;
const tenantId = providerConfig.getString('tenantId');
const clientId = providerConfig.getString('clientId');
const clientSecret = providerConfig.getString('clientSecret');
const clientId = providerConfig.getOptionalString('clientId');
const clientSecret = providerConfig.getOptionalString('clientSecret');
const userExpand = providerConfig.getOptionalString('userExpand');
const userFilter = providerConfig.getOptionalString('userFilter');
@@ -173,6 +175,18 @@ export function readMicrosoftGraphConfig(
throw new Error(`queryMode must be one of: basic, advanced`);
}
if (clientId && !clientSecret) {
throw new Error(
`clientSecret must be provided when clientId is defined.`,
);
}
if (clientSecret && !clientId) {
throw new Error(
`clientId must be provided when clientSecret is defined.`,
);
}
providers.push({
id: target,
target,
@@ -225,14 +239,11 @@ export function readProviderConfig(
config.getOptionalString('target') ?? DEFAULT_TARGET,
'/',
);
const authority = trimEnd(
config.getOptionalString('authority') ?? DEFAULT_AUTHORITY,
'/',
);
const authority = config.getOptionalString('authority');
const clientId = config.getString('clientId');
const clientSecret = config.getString('clientSecret');
const tenantId = config.getString('tenantId');
const clientId = config.getOptionalString('clientId');
const clientSecret = config.getOptionalString('clientSecret');
const userExpand = config.getOptionalString('user.expand');
const userFilter = config.getOptionalString('user.filter');
@@ -260,6 +271,14 @@ export function readProviderConfig(
);
}
if (clientId && !clientSecret) {
throw new Error(`clientSecret must be provided when clientId is defined.`);
}
if (clientSecret && !clientId) {
throw new Error(`clientId must be provided when clientSecret is defined.`);
}
return {
id,
target,
+2 -2
View File
@@ -320,7 +320,7 @@
dependencies:
tslib "^2.2.0"
"@azure/identity@^2.0.1", "@azure/identity@^2.0.4":
"@azure/identity@^2.0.1", "@azure/identity@^2.0.4", "@azure/identity@^2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz#89f0bfc1d1264dfd3d0cb19837c33a9c6706d548"
integrity sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==
@@ -361,7 +361,7 @@
resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.1.0.tgz#b77dbf9ae581f1ed254f81d56422e3cdd6664b32"
integrity sha512-WyfqE5mY/rggjqvq0Q5DxLnA33KSb0vfsUjxa95rycFknI03L5GPYI4HTU9D+g0PL5TtsQGnV3xzAGq9BFCVJQ==
"@azure/msal-node@^1.1.0", "@azure/msal-node@^1.10.0":
"@azure/msal-node@^1.10.0":
version "1.11.0"
resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.11.0.tgz#d8bd3f15c1f05bf806ba6f9479c48c2eddd6a98d"
integrity sha512-KW/XEexfCrPzdYbjY7NVmhq9okZT3Jvck55CGXpz9W5asxeq3EtrP45p+ZXtQVEfko0YJdolpCNqWUyXvanWZg==