Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

port:[#3906] Port Managed Identity (MSI) + Single Tenant from DotNet #3923

Merged
merged 33 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2253145
Port MSI and SingleTenant support
ceciliaavila Sep 2, 2021
189f9d2
Add unit tests for botframework-connector
ceciliaavila Sep 2, 2021
d88c616
Restore yarn.lock
ceciliaavila Sep 3, 2021
7c45a2b
Test nohoist for azure/identity
ceciliaavila Sep 3, 2021
542a7ab
Fix dependencies issues
sw-joelmut Sep 7, 2021
f3dd2c3
Connect ManagedIdentityAuthenticator.getToken
sw-joelmut Sep 7, 2021
86f0f2d
Add remaining tests and fix the already added
sw-joelmut Sep 7, 2021
e0a233a
Update yarn.lock
sw-joelmut Sep 7, 2021
171733b
Fixing depcheck errors in PR
MartinLuccanera Sep 9, 2021
875b0d3
Fixing depcheck, adjusting versions and ignores
MartinLuccanera Sep 9, 2021
288765f
Fix parity port with DotNet causing SingleTenant to fail at authentic…
sw-joelmut Sep 9, 2021
0c4a9a6
Merge branch 'southworks/add/port-msi-single-tenant-support' of https…
sw-joelmut Sep 9, 2021
135522d
Apppliying feedback
MartinLuccanera Sep 10, 2021
14386eb
Merge branch 'southworks/add/port-msi-single-tenant-support' of githu…
MartinLuccanera Sep 10, 2021
afebb4d
Applying fixes for tests, lint and zod related feedback
MartinLuccanera Sep 13, 2021
bc25476
Fixing asserts import and extra parameter
MartinLuccanera Sep 14, 2021
799842f
Merge branch 'southworks/add/port-msi-single-tenant-support' of githu…
MartinLuccanera Sep 14, 2021
a6132b6
Fix tests failing due to wrong link and fixing case-wise comparison f…
MartinLuccanera Sep 15, 2021
5c95709
Fixing lint
MartinLuccanera Sep 15, 2021
4d54c25
Merge branch 'main' into southworks/add/port-msi-single-tenant-support
ceciliaavila Sep 15, 2021
12fe13f
Add ignore-casing to MicrosoftAppType based on DotNet code and add un…
sw-joelmut Sep 16, 2021
c78713a
Improve issuer array assignation
sw-joelmut Sep 16, 2021
7bbf539
Merge remote-tracking branch 'upstream/southworks/add/port-msi-single…
sw-joelmut Sep 16, 2021
0e207e5
fix: export ms-rest-js type and remove added dep
Sep 17, 2021
9f75226
Improve issuers, assertions, unit tests and many other small changes
sw-joelmut Sep 20, 2021
27fbdf0
fix: skillValidation validTokenIssuers
sw-joelmut Sep 20, 2021
1fa5179
Merge branch 'main' into southworks/add/port-msi-single-tenant-support
sw-joelmut Sep 21, 2021
a8a8277
Update @azure/identity to 2.0.0-beta.6
sw-joelmut Sep 21, 2021
9c9f8d9
Change logical operator for nullable operator
sw-joelmut Sep 21, 2021
2f88d99
Merge branch 'main' into southworks/add/port-msi-single-tenant-support
sw-joelmut Sep 24, 2021
3245f18
Improve SingleTenant and MSI implementation from feedback
sw-joelmut Sep 24, 2021
47d891f
fix: IJwtTokenProviderFactory
Sep 27, 2021
5f66d89
fix: reintroduced typo
Sep 27, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libraries/botbuilder-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
}
},
"dependencies": {
"@azure/ms-rest-js": "1.9.1",
sw-joelmut marked this conversation as resolved.
Show resolved Hide resolved
"botbuilder-dialogs-adaptive-runtime-core": "4.1.6",
"botbuilder-stdlib": "4.1.6",
"botframework-connector": "4.1.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

import * as z from 'zod';
import assert from 'assert';
import { Configuration } from 'botbuilder-dialogs-adaptive-runtime-core';
import {
ManagedIdentityServiceClientCredentialsFactory,
Expand Down Expand Up @@ -82,35 +83,19 @@ export class ConfigurationServiceClientCredentialFactory extends PasswordService

switch (appType) {
case MicrosoftAppTypes.UserAssignedMsi:
if (!MicrosoftAppId || MicrosoftAppId.trim() === '') {
throw new Error('MicrosoftAppId is required for MSI in configuration.');
}

if (!MicrosoftAppTenantId || MicrosoftAppTenantId.trim() === '') {
throw new Error('MicrosoftAppTenantId is required for MSI in configuration.');
}

if (MicrosoftAppPassword && MicrosoftAppPassword.trim() !== '') {
throw new Error('MicrosoftAppPassword must not be set for MSI in configuration.');
}
assert(MicrosoftAppId?.trim(), 'MicrosoftAppId is required for MSI in configuration.');
assert(MicrosoftAppTenantId?.trim(), 'MicrosoftAppTenantId is required for MSI in configuration.');
assert(!MicrosoftAppPassword?.trim(), 'MicrosoftAppPassword must not be set for MSI in configuration.');

this.inner = new ManagedIdentityServiceClientCredentialsFactory(
MicrosoftAppId,
new JwtTokenProviderFactory()
);
break;
case MicrosoftAppTypes.SingleTenant:
if (!MicrosoftAppId || MicrosoftAppId.trim() === '') {
throw new Error('MicrosoftAppId is required for SingleTenant in configuration.');
}

if (!MicrosoftAppTenantId || MicrosoftAppTenantId.trim() === '') {
throw new Error('MicrosoftAppTenantId is required for SingleTenant in configuration.');
}

if (!MicrosoftAppPassword || MicrosoftAppPassword.trim() === '') {
throw new Error('MicrosoftAppPassword is required for SingleTenant in configuration.');
}
assert(MicrosoftAppId?.trim(), 'MicrosoftAppId is required for SingleTenant in configuration.');
assert(MicrosoftAppPassword?.trim(), 'MicrosoftAppPassword is required for SingleTenant in configuration.');
assert(MicrosoftAppTenantId?.trim(), 'MicrosoftAppTenantId is required for SingleTenant in configuration.');

this.inner = new PasswordServiceClientCredentialFactory(
MicrosoftAppId,
Expand Down
2 changes: 1 addition & 1 deletion libraries/botframework-connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"build:browserify:run": "browserify -s BFC --debug lib/browser.js | exorcist lib/browser.js.map | sponge lib/browser.js",
"build:downlevel-dts": "downlevel-dts lib _ts3.4/lib --checksum",
"clean": "rimraf _ts3.4 lib tsconfig.tsbuildinfo",
"depcheck": "depcheck --config ../../.depcheckrc --ignores azure",
"depcheck": "depcheck --config ../../.depcheckrc --ignores azure,sinon",
"lint": "eslint . --ext .js,.ts",
"postbuild": "npm-run-all -p build:browserify build:downlevel-dts",
"test": "yarn build && yarn test:mocha",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import { Claim } from './claimsIdentity';
*/
export type ValidateClaims = (claims: Claim[]) => Promise<void>;

/**
* Used to contain a collection of valid JWT token issuers.
*/
export type ValidTokenIssuers = string[];

/**
* General configuration settings for authentication.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { DefaultAzureCredential } from '@azure/identity';
import { JwtTokenProviderFactoryInterface } from './jwtTokenProviderFactoryInterface';
import * as assert from 'assert';
MartinLuccanera marked this conversation as resolved.
Show resolved Hide resolved

/**
* @inheritdoc
Expand All @@ -17,9 +18,7 @@ export class JwtTokenProviderFactory implements JwtTokenProviderFactoryInterface
* @inheritdoc
*/
createAzureServiceTokenProvider(appId: string): DefaultAzureCredential {
if (!appId || appId.trim() === '') {
throw new Error('jwtTokenProviderFactory.createAzureServiceTokenProvider(): missing appid.');
}
assert(appId?.trim(), 'jwtTokenProviderFactory.createAzureServiceTokenProvider(): missing appid.');

return new DefaultAzureCredential({ managedIdentityClientId: appId });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TokenResponse } from 'adal-node';
import { AppCredentials } from './appCredentials';
import { JwtTokenProviderFactoryInterface } from './jwtTokenProviderFactoryInterface';
import { ManagedIdentityAuthenticator } from './managedIdentityAuthenticator';
import * as assert from 'assert';

/**
* Managed Service Identity auth implementation.
Expand All @@ -28,13 +29,8 @@ export class ManagedIdentityAppCredentials extends AppCredentials {
constructor(appId: string, oAuthScope: string, tokenProviderFactory: JwtTokenProviderFactoryInterface) {
super(appId, null, oAuthScope);

if (!appId || appId.trim() === '') {
throw new Error('ManagedIdentityAppCredentials.constructor(): missing appid.');
}

if (!tokenProviderFactory) {
throw new Error('ManagedIdentityAppCredentials.constructor(): missing tokenProviderFactory.');
}
assert(appId?.trim(), 'ManagedIdentityAppCredentials.constructor(): missing appid.');
assert(tokenProviderFactory, 'ManagedIdentityAppCredentials.constructor(): missing tokenProviderFactory.');

this.tokenProviderFactory = tokenProviderFactory;
super.appId = appId;
Expand All @@ -44,13 +40,11 @@ export class ManagedIdentityAppCredentials extends AppCredentials {
* @inheritdoc
*/
protected async refreshToken(): Promise<TokenResponse> {
if (!this.authenticator) {
this.authenticator = new ManagedIdentityAuthenticator(
this.appId,
this.oAuthScope,
this.tokenProviderFactory
);
}
this.authenticator ??= new ManagedIdentityAuthenticator(
this.appId,
this.oAuthScope,
this.tokenProviderFactory
);

const token = await this.authenticator.getToken();
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
*/

import { AccessToken, DefaultAzureCredential } from '@azure/identity';
import { retry } from '../../../botbuilder-stdlib/lib';
import { retry } from 'botbuilder-stdlib';
import { JwtTokenProviderFactoryInterface } from './jwtTokenProviderFactoryInterface';
import * as assert from 'assert';

/**
* Abstraction to acquire tokens from a Managed Service Identity.
Expand All @@ -25,17 +26,9 @@ export class ManagedIdentityAuthenticator {
* @param tokenProviderFactory The JWT token provider factory to use.
*/
constructor(appId: string, resource: string, tokenProviderFactory: JwtTokenProviderFactoryInterface) {
if (!appId || appId.trim() === '') {
throw new Error('ManagedIdentityAuthenticator.constructor(): missing appid.');
}

if (!resource || resource.trim() === '') {
throw new Error('ManagedIdentityAuthenticator.constructor(): missing resource.');
}

if (!tokenProviderFactory) {
throw new Error('ManagedIdentityAuthenticator.constructor(): missing tokenProviderFactory.');
}
assert(appId?.trim(), 'ManagedIdentityAuthenticator.constructor(): missing appid.');
assert(resource?.trim(), 'ManagedIdentityAuthenticator.constructor(): missing resource.');
assert(tokenProviderFactory, 'ManagedIdentityAuthenticator.constructor(): missing tokenProviderFactory.');

this.resource = resource;
this.tokenProvider = tokenProviderFactory.createAzureServiceTokenProvider(appId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class ManagedIdentityServiceClientCredentialsFactory extends ServiceClien
* @inheritdoc
*/
public async createCredentials(appId: string, audience: string): Promise<ServiceClientCredentials> {
if (appId != this.appId) {
if (!await this.isValidAppId(appId)) {
throw new Error('ManagedIdentityServiceClientCredentialsFactory.createCredentials(): Invalid Managed ID.');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,10 @@ export class ParameterizedBotFrameworkAuthentication extends BotFrameworkAuthent
): Promise<ClaimsIdentity> {
// Add allowed token issuers from configuration (if present)
if (this.authConfiguration.validTokenIssuers?.length > 0) {
const validIssuers = Array.from(ToBotFromBotOrEmulatorTokenValidationParameters.issuer);
validIssuers.push(...this.authConfiguration.validTokenIssuers);
ToBotFromBotOrEmulatorTokenValidationParameters.issuer = validIssuers;
ToBotFromBotOrEmulatorTokenValidationParameters.issuer = [
...ToBotFromBotOrEmulatorTokenValidationParameters.issuer,
...this.authConfiguration.validTokenIssuers
]
}

const tokenExtractor = new JwtTokenExtractor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,45 +68,22 @@ export class PasswordServiceClientCredentialFactory implements ServiceClientCred
}

let credentials: MicrosoftAppCredentials;
let normalizedEndpoint = loginEndpoint?.toLowerCase();
const normalizedEndpoint = loginEndpoint?.toLowerCase();

if (normalizedEndpoint?.startsWith(AuthenticationConstants.ToChannelFromBotLoginUrlPrefix)) {
// TODO: Unpack necessity of these empty credentials based on the loginEndpoint as no tokens are fetched when auth is disabled.
credentials =
appId == null
? MicrosoftAppCredentials.Empty
: new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience);
credentials = new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience);
} else if (normalizedEndpoint === GovernmentConstants.ToChannelFromBotLoginUrl.toLowerCase()) {
credentials =
appId == null
? new MicrosoftAppCredentials(
undefined,
undefined,
undefined,
GovernmentConstants.ToChannelFromBotOAuthScope
)
: new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience);
normalizedEndpoint = loginEndpoint;
credentials = new MicrosoftAppCredentials(appId, this.password, this.tenantId, audience);
} else {
credentials =
appId == null
? new PrivateCloudAppCredentials(
undefined,
undefined,
undefined,
undefined,
normalizedEndpoint,
validateAuthority
)
: new PrivateCloudAppCredentials(
appId,
this.password,
this.tenantId,
audience,
normalizedEndpoint,
validateAuthority
);
credentials = new PrivateCloudAppCredentials(
appId,
joshgummersall marked this conversation as resolved.
Show resolved Hide resolved
this.password,
this.tenantId,
audience,
normalizedEndpoint,
validateAuthority
);
}
credentials.oAuthEndpoint = normalizedEndpoint;
return credentials;
}
}
Expand Down