@microsoft/agents-hosting 1.9.0-beta.8.gf58e66b2ee → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -4
- package/dist/package.json +3 -3
- package/dist/src/app/agentApplication.d.ts +4 -3
- package/dist/src/app/agentApplication.js +24 -21
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +9 -2
- package/dist/src/app/agentApplicationBuilder.js +9 -0
- package/dist/src/app/agentApplicationBuilder.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +16 -4
- package/dist/src/app/auth/authorizationManager.d.ts +0 -8
- package/dist/src/app/auth/authorizationManager.js +104 -178
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlerStorage.d.ts +5 -3
- package/dist/src/app/auth/handlerStorage.js +36 -10
- package/dist/src/app/auth/handlerStorage.js.map +1 -1
- package/dist/src/app/auth/types.d.ts +2 -2
- package/dist/src/app/index.d.ts +1 -0
- package/dist/src/app/proactive/proactive.js +10 -6
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/proactive/proactiveOptions.d.ts +2 -2
- package/dist/src/app/rateLimit.d.ts +315 -0
- package/dist/src/app/rateLimit.js +333 -0
- package/dist/src/app/rateLimit.js.map +1 -0
- package/dist/src/app/turnState.d.ts +6 -3
- package/dist/src/app/turnState.js +43 -4
- package/dist/src/app/turnState.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +10 -3
- package/dist/src/auth/authConfiguration.js +292 -259
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/settings.d.ts +8 -70
- package/dist/src/auth/settings.js +5 -67
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +5 -0
- package/dist/src/cloudAdapter.js +13 -103
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.d.ts +23 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js +183 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js.map +1 -0
- package/dist/src/configuration/configuration.d.ts +83 -0
- package/dist/src/configuration/configuration.js +836 -0
- package/dist/src/configuration/configuration.js.map +1 -0
- package/dist/src/configuration/configurationSource.d.ts +57 -0
- package/dist/src/configuration/configurationSource.js +7 -0
- package/dist/src/configuration/configurationSource.js.map +1 -0
- package/dist/src/configuration/environmentConfiguration.d.ts +14 -0
- package/dist/src/configuration/environmentConfiguration.js +271 -0
- package/dist/src/configuration/environmentConfiguration.js.map +1 -0
- package/dist/src/configuration/index.d.ts +7 -0
- package/dist/src/configuration/index.js +14 -0
- package/dist/src/configuration/index.js.map +1 -0
- package/dist/src/configuration/jsonFileConfigurationSource.d.ts +15 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js +58 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js.map +1 -0
- package/dist/src/createCloudAdapter.d.ts +30 -5
- package/dist/src/createCloudAdapter.js +17 -10
- package/dist/src/createCloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.js +117 -5
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/observability/traces.d.ts +6 -4
- package/dist/src/observability/traces.js +10 -9
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +4 -1
- package/dist/src/outboundHostValidator.js +25 -48
- package/dist/src/outboundHostValidator.js.map +1 -1
- package/dist/src/state/agentState.d.ts +8 -3
- package/dist/src/state/agentState.js +26 -8
- package/dist/src/state/agentState.js.map +1 -1
- package/dist/src/state/conversationState.d.ts +2 -2
- package/dist/src/state/conversationState.js.map +1 -1
- package/dist/src/state/userState.d.ts +2 -2
- package/dist/src/state/userState.js.map +1 -1
- package/dist/src/statusCodes.d.ts +5 -0
- package/dist/src/statusCodes.js +5 -0
- package/dist/src/statusCodes.js.map +1 -1
- package/dist/src/storage/fileStorage.d.ts +83 -68
- package/dist/src/storage/fileStorage.js +279 -105
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/index.d.ts +1 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/index.js.map +1 -1
- package/dist/src/storage/memoryStorage.d.ts +85 -60
- package/dist/src/storage/memoryStorage.js +244 -87
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/storage/storage.d.ts +100 -2
- package/dist/src/storage/storage.js +25 -0
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/storage/storageCompatibility.d.ts +17 -0
- package/dist/src/storage/storageCompatibility.js +157 -0
- package/dist/src/storage/storageCompatibility.js.map +1 -0
- package/dist/src/storage/storageExpiry.d.ts +6 -0
- package/dist/src/storage/storageExpiry.js +20 -0
- package/dist/src/storage/storageExpiry.js.map +1 -0
- package/dist/src/utils/env.d.ts +43 -0
- package/dist/src/utils/env.js +42 -6
- package/dist/src/utils/env.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +10 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentApplication.ts +27 -14
- package/src/app/agentApplicationBuilder.ts +13 -2
- package/src/app/agentApplicationOptions.ts +18 -4
- package/src/app/auth/authorizationManager.ts +126 -204
- package/src/app/auth/handlerStorage.ts +51 -7
- package/src/app/auth/types.ts +2 -2
- package/src/app/index.ts +7 -0
- package/src/app/proactive/proactive.ts +19 -9
- package/src/app/proactive/proactiveOptions.ts +2 -2
- package/src/app/rateLimit.ts +659 -0
- package/src/app/turnState.ts +50 -7
- package/src/auth/authConfiguration.ts +415 -260
- package/src/auth/settings.ts +5 -82
- package/src/cloudAdapter.ts +38 -113
- package/src/configuration/botFrameworkEnvironmentCompatibility.ts +245 -0
- package/src/configuration/configuration.ts +1076 -0
- package/src/configuration/configurationSource.ts +76 -0
- package/src/configuration/environmentConfiguration.ts +377 -0
- package/src/configuration/index.ts +19 -0
- package/src/configuration/jsonFileConfigurationSource.ts +74 -0
- package/src/createCloudAdapter.ts +52 -9
- package/src/errorHelper.ts +143 -5
- package/src/index.ts +1 -0
- package/src/observability/traces.ts +8 -7
- package/src/outboundHostValidator.ts +37 -44
- package/src/state/agentState.ts +39 -13
- package/src/state/conversationState.ts +2 -2
- package/src/state/userState.ts +2 -2
- package/src/statusCodes.ts +6 -0
- package/src/storage/fileStorage.ts +299 -106
- package/src/storage/index.ts +1 -0
- package/src/storage/memoryStorage.ts +268 -85
- package/src/storage/storage.ts +114 -2
- package/src/storage/storageCompatibility.ts +185 -0
- package/src/storage/storageExpiry.ts +21 -0
- package/src/utils/env.ts +63 -0
- package/src/utils.ts +10 -0
|
@@ -20,6 +20,7 @@ import { TranscriptLoggerMiddleware } from '../transcript'
|
|
|
20
20
|
import { CloudAdapter } from '../cloudAdapter'
|
|
21
21
|
import { Authorization, UserAuthorization, AuthorizationManager } from './auth'
|
|
22
22
|
import { Proactive } from './proactive'
|
|
23
|
+
import { AgentApplicationRateLimiter } from './rateLimit'
|
|
23
24
|
import { JwtPayload } from 'jsonwebtoken'
|
|
24
25
|
import { trace, debug, redactString } from '@microsoft/agents-telemetry'
|
|
25
26
|
import { AgentApplicationTraceDefinitions } from '../observability'
|
|
@@ -104,6 +105,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
104
105
|
private readonly _proactive?: Proactive<TState>
|
|
105
106
|
protected readonly _extensions: AgentExtension<TState>[] = []
|
|
106
107
|
private readonly _adaptiveCards: AdaptiveCardsActions<TState>
|
|
108
|
+
private readonly _rateLimiter?: AgentApplicationRateLimiter
|
|
107
109
|
|
|
108
110
|
/**
|
|
109
111
|
* Creates a new instance of AgentApplication.
|
|
@@ -141,6 +143,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
141
143
|
longRunningMessages: options?.longRunningMessages !== undefined ? options.longRunningMessages : false,
|
|
142
144
|
removeRecipientMention: options?.removeRecipientMention !== undefined ? options.removeRecipientMention : true,
|
|
143
145
|
transcriptLogger: options?.transcriptLogger || undefined,
|
|
146
|
+
rateLimit: options?.rateLimit || undefined,
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
this._adaptiveCards = new AdaptiveCardsActions<TState>(this)
|
|
@@ -148,11 +151,20 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
148
151
|
if (this._options.adapter) {
|
|
149
152
|
this._adapter = this._options.adapter
|
|
150
153
|
} else {
|
|
151
|
-
this._adapter = new CloudAdapter(
|
|
154
|
+
this._adapter = new CloudAdapter(
|
|
155
|
+
undefined,
|
|
156
|
+
undefined,
|
|
157
|
+
undefined,
|
|
158
|
+
{ configurationContext: this._options.configurationContext }
|
|
159
|
+
)
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
this._adapter.setAgentName(this._options.agentName)
|
|
155
163
|
|
|
164
|
+
if (this._options.rateLimit && this._options.rateLimit.length > 0) {
|
|
165
|
+
this._rateLimiter = new AgentApplicationRateLimiter(this._options.rateLimit, this._options.storage)
|
|
166
|
+
}
|
|
167
|
+
|
|
156
168
|
// Create Proactive whenever proactive options are explicitly configured or a storage
|
|
157
169
|
// backend is available — no explicit `proactive` option is required.
|
|
158
170
|
if (this._options.proactive !== undefined || this._options.storage !== undefined) {
|
|
@@ -201,6 +213,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
201
213
|
fileDownloaders: this._options.fileDownloaders ? 'configured' : undefined,
|
|
202
214
|
turnStateFactory: options?.turnStateFactory ? 'custom' : 'default',
|
|
203
215
|
adaptiveCardsOptions: this._options.adaptiveCardsOptions,
|
|
216
|
+
rateLimitRules: this._options.rateLimit?.length,
|
|
204
217
|
})
|
|
205
218
|
}
|
|
206
219
|
|
|
@@ -208,7 +221,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
208
221
|
* Gets the authorization instance for the application.
|
|
209
222
|
*
|
|
210
223
|
* @returns The authorization instance.
|
|
211
|
-
* @throws Error if no
|
|
224
|
+
* @throws Error if no authorization handlers were resolved.
|
|
212
225
|
*/
|
|
213
226
|
public get authorization (): Authorization {
|
|
214
227
|
if (this._authorizationManager.handlers.length === 0) {
|
|
@@ -495,7 +508,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
495
508
|
*
|
|
496
509
|
* @param handler - The handler function to be called after successful sign-in.
|
|
497
510
|
* @returns The current instance of the application.
|
|
498
|
-
* @throws Error if
|
|
511
|
+
* @throws Error if no authorization handlers were resolved.
|
|
499
512
|
*
|
|
500
513
|
* @remarks
|
|
501
514
|
* This method allows you to perform actions after a user has successfully authenticated.
|
|
@@ -510,11 +523,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
510
523
|
*
|
|
511
524
|
*/
|
|
512
525
|
public onSignInSuccess (handler: (context: TurnContext, state: TurnState, id?: string) => Promise<void>): this {
|
|
513
|
-
|
|
514
|
-
this.authorization.onSignInSuccess(handler)
|
|
515
|
-
} else {
|
|
516
|
-
throw ExceptionHelper.generateException(Error, Errors.AuthorizationOptionNotAvailable)
|
|
517
|
-
}
|
|
526
|
+
this.authorization.onSignInSuccess(handler)
|
|
518
527
|
return this
|
|
519
528
|
}
|
|
520
529
|
|
|
@@ -523,7 +532,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
523
532
|
*
|
|
524
533
|
* @param handler - The handler function to be called after a failed sign-in attempt.
|
|
525
534
|
* @returns The current instance of the application.
|
|
526
|
-
* @throws Error if
|
|
535
|
+
* @throws Error if no authorization handlers were resolved.
|
|
527
536
|
*
|
|
528
537
|
* @remarks
|
|
529
538
|
* This method allows you to handle cases where a user fails to authenticate,
|
|
@@ -538,11 +547,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
538
547
|
*
|
|
539
548
|
*/
|
|
540
549
|
public onSignInFailure (handler: (context: TurnContext, state: TurnState, id?: string) => Promise<void>): this {
|
|
541
|
-
|
|
542
|
-
this.authorization.onSignInFailure(handler)
|
|
543
|
-
} else {
|
|
544
|
-
throw ExceptionHelper.generateException(Error, Errors.AuthorizationOptionNotAvailable)
|
|
545
|
-
}
|
|
550
|
+
this.authorization.onSignInFailure(handler)
|
|
546
551
|
return this
|
|
547
552
|
}
|
|
548
553
|
|
|
@@ -675,6 +680,14 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
675
680
|
* ```
|
|
676
681
|
*/
|
|
677
682
|
public async runInternal (turnContext: TurnContext): Promise<boolean> {
|
|
683
|
+
if (this._rateLimiter) {
|
|
684
|
+
const rateLimitDecision = await this._rateLimiter.shouldAllowTurn(turnContext)
|
|
685
|
+
if (!rateLimitDecision.allowed) {
|
|
686
|
+
await this._rateLimiter.handleThrottledTurn(turnContext, rateLimitDecision.rule!, rateLimitDecision.result!)
|
|
687
|
+
return false
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
678
691
|
const { authorized, context } = await this.handleAuthorization(turnContext)
|
|
679
692
|
|
|
680
693
|
if (!authorized) {
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { StorageProvider } from '../storage'
|
|
7
7
|
import { AgentApplication } from './agentApplication'
|
|
8
8
|
import { AgentApplicationOptions } from './agentApplicationOptions'
|
|
9
9
|
import { AuthorizationOptions } from './auth/types'
|
|
10
10
|
import { ProactiveOptions } from './proactive'
|
|
11
|
+
import { RateLimitRule } from './rateLimit'
|
|
11
12
|
import { TurnState } from './turnState'
|
|
12
13
|
import { TypingOptions } from './agentApplicationOptions'
|
|
13
14
|
|
|
@@ -31,7 +32,7 @@ export class AgentApplicationBuilder<TState extends TurnState = TurnState> {
|
|
|
31
32
|
* @param storage The storage implementation to use
|
|
32
33
|
* @returns This builder instance for chaining
|
|
33
34
|
*/
|
|
34
|
-
public withStorage (storage:
|
|
35
|
+
public withStorage (storage: StorageProvider): this {
|
|
35
36
|
this._options.storage = storage
|
|
36
37
|
return this
|
|
37
38
|
}
|
|
@@ -86,6 +87,16 @@ export class AgentApplicationBuilder<TState extends TurnState = TurnState> {
|
|
|
86
87
|
return this
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Configures rate limit rules for incoming turns.
|
|
92
|
+
* @param rules The rate limit rules to apply
|
|
93
|
+
* @returns This builder instance for chaining
|
|
94
|
+
*/
|
|
95
|
+
public withRateLimit (rules: RateLimitRule[]): this {
|
|
96
|
+
this._options.rateLimit = rules
|
|
97
|
+
return this
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
/**
|
|
90
101
|
* Builds and returns a new AgentApplication instance configured with the provided options.
|
|
91
102
|
* @returns A new AgentApplication instance
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { CloudAdapter } from '../cloudAdapter'
|
|
7
|
-
import {
|
|
7
|
+
import { StorageProvider } from '../storage'
|
|
8
8
|
import { TranscriptLogger } from '../transcript'
|
|
9
9
|
import { AdaptiveCardsOptions } from './adaptiveCards'
|
|
10
10
|
import { InputFileDownloader } from './inputFileDownloader'
|
|
@@ -13,6 +13,8 @@ import { HeaderPropagationDefinition } from '../headerPropagation'
|
|
|
13
13
|
import { AuthorizationOptions } from './auth/types'
|
|
14
14
|
import { Connections } from '../auth/connections'
|
|
15
15
|
import { ProactiveOptions } from './proactive'
|
|
16
|
+
import { RateLimitRule } from './rateLimit'
|
|
17
|
+
import { ConfigurationContext } from '../configuration/configuration'
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Typing timer settings for a specific channel or the global default.
|
|
@@ -52,6 +54,12 @@ export interface TypingOptions extends TypingTimingOptions {
|
|
|
52
54
|
* custom state management specific to your agent's needs.
|
|
53
55
|
*/
|
|
54
56
|
export interface AgentApplicationOptions<TState extends TurnState> {
|
|
57
|
+
/**
|
|
58
|
+
* Optional host-scoped external configuration used by the application's
|
|
59
|
+
* default adapter and authorization handlers.
|
|
60
|
+
*/
|
|
61
|
+
configurationContext?: ConfigurationContext;
|
|
62
|
+
|
|
55
63
|
/**
|
|
56
64
|
* The adapter used for handling bot interactions with various messaging platforms.
|
|
57
65
|
* This adapter manages the communication layer between your agent and the Bot Framework.
|
|
@@ -72,12 +80,12 @@ export interface AgentApplicationOptions<TState extends TurnState> {
|
|
|
72
80
|
|
|
73
81
|
/**
|
|
74
82
|
* The storage mechanism for persisting conversation and user state across turns.
|
|
75
|
-
* This can be
|
|
76
|
-
*
|
|
83
|
+
* This can be a legacy Storage implementation, a StorageV2 implementation,
|
|
84
|
+
* or a built-in storage provider.
|
|
77
85
|
*
|
|
78
86
|
* @default undefined (no persistence)
|
|
79
87
|
*/
|
|
80
|
-
storage?:
|
|
88
|
+
storage?: StorageProvider;
|
|
81
89
|
|
|
82
90
|
/**
|
|
83
91
|
* Whether to start a typing indicator timer when the bot begins processing a message.
|
|
@@ -201,4 +209,10 @@ export interface AgentApplicationOptions<TState extends TurnState> {
|
|
|
201
209
|
* When provided, `app.proactive` will be available.
|
|
202
210
|
*/
|
|
203
211
|
proactive?: ProactiveOptions
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Optional. Rate limit rules for incoming turns.
|
|
215
|
+
* When omitted or empty, no rate limiting is applied.
|
|
216
|
+
*/
|
|
217
|
+
rateLimit?: RateLimitRule[]
|
|
204
218
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Activity, ExceptionHelper } from '@microsoft/agents-activity'
|
|
7
|
-
import { debug, redactScopes } from '@microsoft/agents-telemetry'
|
|
7
|
+
import { debug, redactScopes, redactString } from '@microsoft/agents-telemetry'
|
|
8
8
|
import { AgentApplication } from '../agentApplication'
|
|
9
9
|
import { AgenticAuthorization, AzureBotAuthorization } from './handlers'
|
|
10
10
|
import { TurnContext } from '../../turnContext'
|
|
@@ -13,8 +13,10 @@ import { Errors } from '../../errorHelper'
|
|
|
13
13
|
import { ActiveAuthorizationHandler, AuthorizationHandlerStatus, AuthorizationHandler, AuthorizationHandlerSettings, AuthorizationOptions } from './types'
|
|
14
14
|
import { Connections } from '../../auth/connections'
|
|
15
15
|
import { sendInvokeResponse } from './utils'
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { mergeDefined, prune } from '../../utils'
|
|
17
|
+
import { getConfigurationSnapshot } from '../../configuration/configuration'
|
|
18
|
+
import { loadModernEnvironmentConfiguration } from '../../configuration/environmentConfiguration'
|
|
19
|
+
import { loadBotFrameworkAuthorizationEnvironmentConfiguration } from '../../configuration/botFrameworkEnvironmentCompatibility'
|
|
18
20
|
|
|
19
21
|
const logger = debug('agents:authorization:manager')
|
|
20
22
|
|
|
@@ -22,6 +24,39 @@ const AGENTIC = 'AgenticUserAuthorization'
|
|
|
22
24
|
const AGENTIC_LEGACY = 'agentic'
|
|
23
25
|
const AZURE_BOT = 'AzureBotUserAuthorization'
|
|
24
26
|
|
|
27
|
+
function redactAuthorizationString (value: unknown): string | undefined {
|
|
28
|
+
return typeof value === 'string' ? redactString(value) : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function summarizeAuthorizationConfiguration (options: AuthorizationOptions[string]) {
|
|
32
|
+
return prune({
|
|
33
|
+
type: options.type,
|
|
34
|
+
azureBotOAuthConnectionName: 'azureBotOAuthConnectionName' in options
|
|
35
|
+
? redactAuthorizationString(options.azureBotOAuthConnectionName)
|
|
36
|
+
: undefined,
|
|
37
|
+
invalidSignInRetryMax: 'invalidSignInRetryMax' in options && typeof options.invalidSignInRetryMax === 'number'
|
|
38
|
+
? options.invalidSignInRetryMax
|
|
39
|
+
: undefined,
|
|
40
|
+
oboConnectionName: 'oboConnectionName' in options
|
|
41
|
+
? redactAuthorizationString(options.oboConnectionName)
|
|
42
|
+
: undefined,
|
|
43
|
+
oboScopes: 'oboScopes' in options && Array.isArray(options.oboScopes) &&
|
|
44
|
+
options.oboScopes.every(scope => typeof scope === 'string')
|
|
45
|
+
? redactScopes(options.oboScopes)
|
|
46
|
+
: undefined,
|
|
47
|
+
enableSso: 'enableSso' in options && typeof options.enableSso === 'boolean'
|
|
48
|
+
? options.enableSso
|
|
49
|
+
: undefined,
|
|
50
|
+
scopes: 'scopes' in options && Array.isArray(options.scopes) &&
|
|
51
|
+
options.scopes.every(scope => typeof scope === 'string')
|
|
52
|
+
? redactScopes(options.scopes)
|
|
53
|
+
: undefined,
|
|
54
|
+
altBlueprintConnectionName: 'altBlueprintConnectionName' in options
|
|
55
|
+
? redactAuthorizationString(options.altBlueprintConnectionName)
|
|
56
|
+
: undefined
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
25
60
|
/**
|
|
26
61
|
* Active handler information used by the AuthorizationManager.
|
|
27
62
|
*/
|
|
@@ -60,92 +95,6 @@ type GetHandlerIds = (activity: Activity) => string[] | Promise<string[]>
|
|
|
60
95
|
export class AuthorizationManager {
|
|
61
96
|
private _handlers: Record<string, AuthorizationHandler> = {}
|
|
62
97
|
|
|
63
|
-
/**
|
|
64
|
-
* Environment variable configuration for the latest format.
|
|
65
|
-
*/
|
|
66
|
-
private _envLatest = {
|
|
67
|
-
key: {
|
|
68
|
-
prefix: 'AgentApplication__UserAuthorization__Handlers__',
|
|
69
|
-
separator: '__Settings__',
|
|
70
|
-
create (id: string, prop: string) {
|
|
71
|
-
return `${this.prefix}${id}${this.separator}${prop}`
|
|
72
|
-
},
|
|
73
|
-
extract (envKey: string) {
|
|
74
|
-
// Substring: AgentApplication__UserAuthorization__Handlers__<id>__Settings__<prop>
|
|
75
|
-
// position —————————————————————————————————————————> start^ ^end ^prop
|
|
76
|
-
const start = this.prefix.length
|
|
77
|
-
const end = envKey.toUpperCase().indexOf(this.separator.toUpperCase())
|
|
78
|
-
if (end === -1) {
|
|
79
|
-
return { id: undefined, prop: undefined }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const id = envKey.substring(start, end)
|
|
83
|
-
const prop = envKey.substring(end + this.separator.length)
|
|
84
|
-
return { id, prop }
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
parser: envParser({
|
|
88
|
-
// Common
|
|
89
|
-
type: envParserUtils.bypass,
|
|
90
|
-
|
|
91
|
-
// Azure Bot
|
|
92
|
-
azureBotOAuthConnectionName: envParserUtils.bypass,
|
|
93
|
-
title: envParserUtils.bypass,
|
|
94
|
-
text: envParserUtils.bypass,
|
|
95
|
-
invalidSignInRetryMessage: envParserUtils.bypass,
|
|
96
|
-
invalidSignInRetryMessageFormat: envParserUtils.bypass,
|
|
97
|
-
invalidSignInRetryMaxExceededMessage: envParserUtils.bypass,
|
|
98
|
-
oboConnectionName: envParserUtils.bypass,
|
|
99
|
-
enableSso (value) {
|
|
100
|
-
return { value: value !== 'false' }
|
|
101
|
-
},
|
|
102
|
-
invalidSignInRetryMax (value) {
|
|
103
|
-
return { value: parseInt(value) }
|
|
104
|
-
},
|
|
105
|
-
oboScopes (value) {
|
|
106
|
-
return this.scopes(value)
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// Agentic
|
|
110
|
-
altBlueprintConnectionName: envParserUtils.bypass,
|
|
111
|
-
scopes (value) {
|
|
112
|
-
if (value.includes(',')) {
|
|
113
|
-
return { value: value.split(',').map(s => s.trim()).filter(Boolean) }
|
|
114
|
-
}
|
|
115
|
-
return { value: value.split(/\s+/).filter(Boolean) }
|
|
116
|
-
}
|
|
117
|
-
}),
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Environment variable configuration for the legacy format.
|
|
122
|
-
*/
|
|
123
|
-
private _envLegacy = {
|
|
124
|
-
key: {
|
|
125
|
-
separator: '_',
|
|
126
|
-
},
|
|
127
|
-
parser: envParser({
|
|
128
|
-
// Common
|
|
129
|
-
type: envParserUtils.redirect(this._envLatest.parser, 'type'),
|
|
130
|
-
|
|
131
|
-
// Azure Bot
|
|
132
|
-
connectionName: envParserUtils.redirect(this._envLatest.parser, 'azureBotOAuthConnectionName'),
|
|
133
|
-
connectionTitle: envParserUtils.redirect(this._envLatest.parser, 'title'),
|
|
134
|
-
connectionText: envParserUtils.redirect(this._envLatest.parser, 'text'),
|
|
135
|
-
maxAttempts: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMax'),
|
|
136
|
-
messages_invalidCode: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMessage'),
|
|
137
|
-
messages_invalidCodeFormat: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMessageFormat'),
|
|
138
|
-
messages_maxAttemptsExceeded: envParserUtils.redirect(this._envLatest.parser, 'invalidSignInRetryMaxExceededMessage'),
|
|
139
|
-
obo_connection: envParserUtils.redirect(this._envLatest.parser, 'oboConnectionName'),
|
|
140
|
-
obo_scopes: envParserUtils.redirect(this._envLatest.parser, 'oboScopes'),
|
|
141
|
-
enableSso: envParserUtils.redirect(this._envLatest.parser, 'enableSso'),
|
|
142
|
-
|
|
143
|
-
// Agentic
|
|
144
|
-
scopes: envParserUtils.redirect(this._envLatest.parser, 'scopes'),
|
|
145
|
-
altBlueprintConnectionName: envParserUtils.redirect(this._envLatest.parser, 'altBlueprintConnectionName')
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
98
|
/**
|
|
150
99
|
* Creates an instance of the AuthorizationManager.
|
|
151
100
|
* @param app The agent application instance.
|
|
@@ -318,82 +267,58 @@ export class AuthorizationManager {
|
|
|
318
267
|
let legacyMessage = ''
|
|
319
268
|
const settings: AuthorizationHandlerSettings = { storage: this.app.options.storage!, connections: this.connections }
|
|
320
269
|
const runtimeOptionEntries = Object.entries(this.app.options.authorization ?? {})
|
|
321
|
-
const result = {
|
|
270
|
+
const result = {
|
|
271
|
+
latest: {},
|
|
272
|
+
legacy: {}
|
|
273
|
+
} as {
|
|
322
274
|
latest: Record<string, Record<string, any> | undefined>;
|
|
323
275
|
legacy: Record<string, Record<string, any> | undefined>;
|
|
324
276
|
}
|
|
277
|
+
const external = getConfigurationSnapshot(this.app.options.configurationContext)
|
|
278
|
+
const externalOptionEntries = [
|
|
279
|
+
...[...external.fallback.agentApplication.userAuthorization.handlers.values()].map(handler => [handler.id, handler.settings] as const),
|
|
280
|
+
...[...external.overrideEnvironment.agentApplication.userAuthorization.handlers.values()].map(handler => [handler.id, handler.settings] as const),
|
|
281
|
+
...[...external.enforce.agentApplication.userAuthorization.handlers.values()].map(handler => [handler.id, handler.settings] as const)
|
|
282
|
+
]
|
|
283
|
+
const modernHandlers = loadModernEnvironmentConfiguration()
|
|
284
|
+
.agentApplication.userAuthorization.handlers
|
|
285
|
+
for (const handler of modernHandlers.values()) {
|
|
286
|
+
result.latest[handler.id] = { ...handler.settings }
|
|
287
|
+
}
|
|
325
288
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (!upperEnvKey.startsWith(this._envLatest.key.prefix.toUpperCase())) {
|
|
335
|
-
const [id] = runtimeOptionEntries.find(([id]) => upperEnvKey.startsWith(`${id.toUpperCase()}${this._envLegacy.key.separator}`)) ?? []
|
|
336
|
-
if (!id) {
|
|
337
|
-
continue
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const prop = envKey.substring(id.length + this._envLegacy.key.separator.length)
|
|
341
|
-
if (!prop) {
|
|
342
|
-
continue
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const { key, value } = this._envLegacy.parser.parse(prop as any, envValue)
|
|
346
|
-
if (!key) {
|
|
347
|
-
continue
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
legacyMessage += ` ${envKey}= # Use ${this._envLatest.key.create(id, key)} instead.\n`
|
|
351
|
-
|
|
352
|
-
result.legacy[id] ??= {}
|
|
353
|
-
result.legacy[id][key] = value
|
|
354
|
-
continue
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Latest: extract handler ID, handler options key and its value, and assign it to the correct latest handler ID.
|
|
358
|
-
const { id, prop } = this._envLatest.key.extract(envKey)
|
|
359
|
-
if (!id || !prop) {
|
|
360
|
-
continue
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const { key, value } = this._envLatest.parser.parse(prop as any, envValue)
|
|
364
|
-
if (!key) {
|
|
365
|
-
continue
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Find existing handler ID case-insensitively to avoid duplicates, but keep the original casing for later processing.
|
|
369
|
-
// This allows users to specify environment variables in a case-insensitive way, while ensuring that the handler IDs are treated in a case-sensitive way internally, to avoid issues when referencing them later.
|
|
370
|
-
// For example, if the user specifies AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__GRAPH__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME and agentapplication__userauthorization__handlers__graph__settings__azurebotoauthconnectionname, we want to treat them as the same handler ID "graph", and not create two separate handlers "GRAPH" and "graph".
|
|
371
|
-
// Note: the first environment variable that is processed will determine the casing of the handler ID, and any subsequent environment variable that matches the same handler ID case-insensitively will be treated as referring to the same handler, regardless of its casing.
|
|
372
|
-
const existingId = id in result.latest ? id : Object.keys(result.latest).find(e => e.toLowerCase() === id.toLowerCase())
|
|
373
|
-
const realId = existingId ?? id
|
|
374
|
-
result.latest[realId] ??= {}
|
|
375
|
-
result.latest[realId][key] = value
|
|
289
|
+
const compatibility = loadBotFrameworkAuthorizationEnvironmentConfiguration(
|
|
290
|
+
[...runtimeOptionEntries, ...externalOptionEntries].map(([id]) => id)
|
|
291
|
+
)
|
|
292
|
+
for (const handler of compatibility.layer.agentApplication.userAuthorization.handlers.values()) {
|
|
293
|
+
result.legacy[handler.id] = { ...handler.settings }
|
|
294
|
+
}
|
|
295
|
+
for (const replacement of compatibility.replacements) {
|
|
296
|
+
legacyMessage += ` ${replacement.legacyKey}= # Use ${replacement.modernKey} instead.\n`
|
|
376
297
|
}
|
|
377
298
|
|
|
378
299
|
if (legacyMessage.length > 0) {
|
|
379
300
|
logger.warn('Deprecated environment variables detected, update to the latest format: (case-insensitive)', `[\n${legacyMessage}]`)
|
|
380
301
|
}
|
|
381
302
|
|
|
382
|
-
// Fix types
|
|
383
|
-
const fixTypes = (entries: [string, any][]) => entries
|
|
384
|
-
.map(([id, options]) => [id, { ...options, type: this.fixType(id, options?.type) }] as [string, AuthorizationOptions[string]])
|
|
385
|
-
|
|
386
|
-
const runtimeEntries = fixTypes(runtimeOptionEntries)
|
|
387
|
-
const latestEntries = fixTypes(Object.entries(result.latest))
|
|
388
|
-
const legacyEntries = fixTypes(Object.entries(result.legacy))
|
|
389
|
-
|
|
390
303
|
const registeredHandlers = new Set()
|
|
391
|
-
|
|
304
|
+
const handlerEntries = [
|
|
305
|
+
...runtimeOptionEntries,
|
|
306
|
+
...Object.entries(result.latest),
|
|
307
|
+
...Object.entries(result.legacy),
|
|
308
|
+
...externalOptionEntries
|
|
309
|
+
]
|
|
310
|
+
for (const [id] of handlerEntries) {
|
|
392
311
|
if (registeredHandlers.has(id.toLowerCase())) {
|
|
393
312
|
continue
|
|
394
313
|
}
|
|
395
314
|
|
|
396
|
-
const { options, format } = this.resolveHandlerConfiguration(
|
|
315
|
+
const { options, format } = this.resolveHandlerConfiguration(
|
|
316
|
+
id,
|
|
317
|
+
runtimeOptionEntries,
|
|
318
|
+
Object.entries(result.latest),
|
|
319
|
+
Object.entries(result.legacy),
|
|
320
|
+
external
|
|
321
|
+
)
|
|
397
322
|
|
|
398
323
|
if (options.type === AZURE_BOT) {
|
|
399
324
|
// Set default values if not provided
|
|
@@ -403,17 +328,11 @@ export class AuthorizationManager {
|
|
|
403
328
|
options.enableSso = options.enableSso !== false // default value is true if undefined.
|
|
404
329
|
}
|
|
405
330
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if ('scopes' in options && options.scopes && options.scopes.length > 0) {
|
|
413
|
-
maskedOptions.scopes = redactScopes(options.scopes)
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
logger.info(this.prefix(id, 'settings loaded from \'%s\''), format, { ...options, ...maskedOptions })
|
|
331
|
+
logger.info(
|
|
332
|
+
this.prefix(id, 'settings loaded from \'%s\''),
|
|
333
|
+
format,
|
|
334
|
+
summarizeAuthorizationConfiguration(options)
|
|
335
|
+
)
|
|
417
336
|
|
|
418
337
|
if (!settings.storage) {
|
|
419
338
|
throw ExceptionHelper.generateException(Error, Errors.StorageRequiredForAuthorization)
|
|
@@ -458,62 +377,65 @@ export class AuthorizationManager {
|
|
|
458
377
|
*/
|
|
459
378
|
private resolveHandlerConfiguration (
|
|
460
379
|
id: string,
|
|
461
|
-
runtimeEntries: Array<[string,
|
|
462
|
-
latestEntries: Array<[string,
|
|
463
|
-
legacyEntries: Array<[string,
|
|
380
|
+
runtimeEntries: Array<[string, any]>,
|
|
381
|
+
latestEntries: Array<[string, any]>,
|
|
382
|
+
legacyEntries: Array<[string, any]>,
|
|
383
|
+
external: ReturnType<typeof getConfigurationSnapshot>
|
|
464
384
|
): {
|
|
465
385
|
options: AuthorizationOptions[string];
|
|
466
386
|
format: string;
|
|
467
387
|
} {
|
|
468
|
-
const matchesId = ([_id]: [string,
|
|
388
|
+
const matchesId = ([_id]: [string, any]) => _id.toLowerCase() === id.toLowerCase()
|
|
389
|
+
const find = (entries: Array<[string, any]>) => entries.find(matchesId)?.[1]
|
|
390
|
+
const findExternal = (
|
|
391
|
+
entries: ReadonlyMap<string, Readonly<{ id: string; settings: Readonly<Record<string, unknown>> }>>
|
|
392
|
+
) => [...entries.values()].find(handler => handler.id.toLowerCase() === id.toLowerCase())?.settings
|
|
469
393
|
|
|
470
|
-
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
const [, legacy] = legacyEntries.find(matchesId) ?? []
|
|
394
|
+
const runtime = find(runtimeEntries)
|
|
395
|
+
const latest = find(latestEntries)
|
|
396
|
+
const legacy = find(legacyEntries)
|
|
474
397
|
|
|
475
398
|
if (runtime !== undefined && latest !== undefined) {
|
|
476
399
|
logger.warn(this.prefix(id, 'Both runtime options and latest environment variable configurations detected. Runtime configuration will take precedence over latest environment variables.'))
|
|
477
400
|
}
|
|
478
401
|
|
|
402
|
+
const runtimeOptions = runtime === undefined ? undefined : { ...runtime }
|
|
479
403
|
const runtimeLegacyKeys = ['name', 'maxAttempts', 'messages', 'obo']
|
|
480
|
-
const isRuntimeLegacy =
|
|
481
|
-
|
|
482
|
-
let
|
|
483
|
-
if (isRuntimeLegacy &&
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
delete
|
|
492
|
-
delete
|
|
493
|
-
delete
|
|
494
|
-
delete
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (runtime !== undefined && legacy !== undefined) {
|
|
499
|
-
return { format: `${runtimeLegacyFormat || 'runtime options'} + .env variables (legacy)`, options: { ...prune(legacy), ...prune(runtime) } }
|
|
404
|
+
const isRuntimeLegacy = runtimeOptions !== undefined && Object.keys(runtimeOptions).some(key => runtimeLegacyKeys.includes(key))
|
|
405
|
+
|
|
406
|
+
let runtimeFormat = 'runtime options'
|
|
407
|
+
if (isRuntimeLegacy && this.fixType(id, runtimeOptions.type) === AZURE_BOT) {
|
|
408
|
+
runtimeOptions.azureBotOAuthConnectionName ??= runtimeOptions.name
|
|
409
|
+
runtimeOptions.invalidSignInRetryMax ??= runtimeOptions.maxAttempts
|
|
410
|
+
runtimeOptions.invalidSignInRetryMessage ??= runtimeOptions.messages?.invalidCode
|
|
411
|
+
runtimeOptions.invalidSignInRetryMessageFormat ??= runtimeOptions.messages?.invalidCodeFormat
|
|
412
|
+
runtimeOptions.invalidSignInRetryMaxExceededMessage ??= runtimeOptions.messages?.maxAttemptsExceeded
|
|
413
|
+
runtimeOptions.oboConnectionName ??= runtimeOptions.obo?.connection
|
|
414
|
+
runtimeOptions.oboScopes ??= runtimeOptions.obo?.scopes
|
|
415
|
+
delete runtimeOptions.name
|
|
416
|
+
delete runtimeOptions.maxAttempts
|
|
417
|
+
delete runtimeOptions.messages
|
|
418
|
+
delete runtimeOptions.obo
|
|
419
|
+
runtimeFormat = 'runtime options (legacy)'
|
|
500
420
|
}
|
|
501
421
|
|
|
502
|
-
|
|
503
|
-
|
|
422
|
+
const layers: Array<[string, Record<string, any> | undefined]> = [
|
|
423
|
+
['external configuration (fallback)', findExternal(external.fallback.agentApplication.userAuthorization.handlers)],
|
|
424
|
+
['.env variables (legacy)', runtimeOptions !== undefined || latest === undefined ? legacy : undefined],
|
|
425
|
+
['.env variables', runtimeOptions === undefined ? latest : undefined],
|
|
426
|
+
['external configuration (overrideEnvironment)', findExternal(external.overrideEnvironment.agentApplication.userAuthorization.handlers)],
|
|
427
|
+
[runtimeFormat, runtimeOptions],
|
|
428
|
+
['external configuration (enforce)', findExternal(external.enforce.agentApplication.userAuthorization.handlers)]
|
|
429
|
+
]
|
|
430
|
+
const activeLayers = layers.filter((layer): layer is [string, Record<string, any>] => layer[1] !== undefined)
|
|
431
|
+
const options = mergeDefined<AuthorizationOptions[string]>(
|
|
432
|
+
...activeLayers.map(([, layer]) => layer)
|
|
433
|
+
)
|
|
434
|
+
options.type = this.fixType(id, options.type)
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
format: activeLayers.map(([format]) => format).join(' + ') || 'empty options',
|
|
438
|
+
options
|
|
504
439
|
}
|
|
505
|
-
|
|
506
|
-
if (latest !== undefined) {
|
|
507
|
-
return { format: '.env variables', options: prune(latest) }
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (legacy !== undefined) {
|
|
511
|
-
// Note: this should never happen, as legacy requires runtime to be present.
|
|
512
|
-
// However, we show a warning just in case to detect if something goes wrong.
|
|
513
|
-
logger.warn(this.prefix(id, 'Legacy environment variable configuration detected without a corresponding runtime options configuration.'))
|
|
514
|
-
return { format: '.env variables (legacy)', options: prune(legacy) }
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
return { format: 'empty options', options: {} }
|
|
518
440
|
}
|
|
519
441
|
}
|