@microsoft/agents-hosting 1.9.0-beta.3.gb2d23c1c44 → 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/authorization.d.ts +54 -0
- package/dist/src/app/auth/authorization.js +63 -2
- package/dist/src/app/auth/authorization.js.map +1 -1
- 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/delegatedTokenCredential.d.ts +29 -0
- package/dist/src/auth/delegatedTokenCredential.js +49 -0
- package/dist/src/auth/delegatedTokenCredential.js.map +1 -0
- package/dist/src/auth/msal/msalTokenCredential.js +1 -1
- package/dist/src/auth/msal/msalTokenCredential.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 +124 -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/authorization.ts +67 -2
- 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/delegatedTokenCredential.ts +49 -0
- package/src/auth/msal/msalTokenCredential.ts +1 -1
- 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 +151 -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
|
@@ -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
|
}
|
|
@@ -5,10 +5,19 @@
|
|
|
5
5
|
|
|
6
6
|
import { ActiveAuthorizationHandler } from './types'
|
|
7
7
|
import { TurnContext } from '../../turnContext'
|
|
8
|
-
import {
|
|
8
|
+
import { StorageProvider, StorageV2 } from '../../storage'
|
|
9
|
+
import {
|
|
10
|
+
asStorageV2,
|
|
11
|
+
assertStorageDeleteSucceeded,
|
|
12
|
+
assertStorageWriteSucceeded,
|
|
13
|
+
getStorageReadValue,
|
|
14
|
+
} from '../../storage/storageCompatibility'
|
|
9
15
|
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
10
16
|
import { Errors } from '../../errorHelper'
|
|
11
17
|
|
|
18
|
+
// Version metadata only bridges reads and writes in one turn. Cloned contexts share turnState.
|
|
19
|
+
const HANDLER_STORAGE_VERSIONS = Symbol('handlerStorageVersions')
|
|
20
|
+
|
|
12
21
|
/**
|
|
13
22
|
* Storage manager for handler state.
|
|
14
23
|
*/
|
|
@@ -18,7 +27,11 @@ export class HandlerStorage<TActiveHandler extends ActiveAuthorizationHandler =
|
|
|
18
27
|
* @param storage The storage provider.
|
|
19
28
|
* @param context The turn context.
|
|
20
29
|
*/
|
|
21
|
-
|
|
30
|
+
private readonly storage: StorageV2
|
|
31
|
+
|
|
32
|
+
constructor (storage: StorageProvider, private context: TurnContext) {
|
|
33
|
+
this.storage = asStorageV2(storage)
|
|
34
|
+
}
|
|
22
35
|
|
|
23
36
|
/**
|
|
24
37
|
* Gets the unique key for a handler session.
|
|
@@ -36,15 +49,23 @@ export class HandlerStorage<TActiveHandler extends ActiveAuthorizationHandler =
|
|
|
36
49
|
* Reads the active handler state from storage.
|
|
37
50
|
*/
|
|
38
51
|
public async read (): Promise<TActiveHandler | undefined> {
|
|
39
|
-
const ongoing = await this.storage.read([this.key])
|
|
40
|
-
|
|
52
|
+
const ongoing = await this.storage.read<TActiveHandler>([this.key])
|
|
53
|
+
const value = getStorageReadValue(ongoing, this.key)
|
|
54
|
+
this.version = ongoing[this.key]?.version
|
|
55
|
+
return value
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
/**
|
|
44
59
|
* Writes handler state to storage.
|
|
45
60
|
*/
|
|
46
|
-
public write (data: TActiveHandler) : Promise<void> {
|
|
47
|
-
|
|
61
|
+
public async write (data: TActiveHandler) : Promise<void> {
|
|
62
|
+
const { eTag: _eTag, ...value } = data
|
|
63
|
+
const results = await this.storage.write(
|
|
64
|
+
{ [this.key]: value },
|
|
65
|
+
this.version === undefined ? undefined : { expectedVersion: this.version }
|
|
66
|
+
)
|
|
67
|
+
assertStorageWriteSucceeded(results, [this.key])
|
|
68
|
+
this.version = results[this.key]?.version
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
/**
|
|
@@ -52,7 +73,9 @@ export class HandlerStorage<TActiveHandler extends ActiveAuthorizationHandler =
|
|
|
52
73
|
*/
|
|
53
74
|
public async delete (): Promise<void> {
|
|
54
75
|
try {
|
|
55
|
-
await this.storage.delete([this.key])
|
|
76
|
+
const results = await this.storage.delete([this.key])
|
|
77
|
+
assertStorageDeleteSucceeded(results, [this.key])
|
|
78
|
+
this.version = undefined
|
|
56
79
|
} catch (error) {
|
|
57
80
|
if (error instanceof Error && 'code' in error && error.code === 404) {
|
|
58
81
|
return
|
|
@@ -60,4 +83,25 @@ export class HandlerStorage<TActiveHandler extends ActiveAuthorizationHandler =
|
|
|
60
83
|
throw error
|
|
61
84
|
}
|
|
62
85
|
}
|
|
86
|
+
|
|
87
|
+
private get version (): string | undefined {
|
|
88
|
+
return this.context.turnState.get<Map<string, string>>(HANDLER_STORAGE_VERSIONS)?.get(this.key)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private set version (value: string | undefined) {
|
|
92
|
+
let versions = this.context.turnState.get<Map<string, string>>(HANDLER_STORAGE_VERSIONS)
|
|
93
|
+
if (value === undefined) {
|
|
94
|
+
versions?.delete(this.key)
|
|
95
|
+
if (versions?.size === 0) {
|
|
96
|
+
this.context.turnState.delete(HANDLER_STORAGE_VERSIONS)
|
|
97
|
+
}
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!versions) {
|
|
102
|
+
versions = new Map<string, string>()
|
|
103
|
+
this.context.turnState.set(HANDLER_STORAGE_VERSIONS, versions)
|
|
104
|
+
}
|
|
105
|
+
versions.set(this.key, value)
|
|
106
|
+
}
|
|
63
107
|
}
|
package/src/app/auth/types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Activity } from '@microsoft/agents-activity'
|
|
7
|
-
import {
|
|
7
|
+
import { StorageProvider, StoreItem } from '../../storage'
|
|
8
8
|
import { TurnContext } from '../../turnContext'
|
|
9
9
|
import { AgenticAuthorizationOptions, AzureBotAuthorizationOptions } from './handlers'
|
|
10
10
|
import { TokenResponse } from '../../oauth'
|
|
@@ -97,7 +97,7 @@ export interface AuthorizationHandlerSettings {
|
|
|
97
97
|
/**
|
|
98
98
|
* Storage instance for persisting handler state.
|
|
99
99
|
*/
|
|
100
|
-
storage:
|
|
100
|
+
storage: StorageProvider
|
|
101
101
|
/**
|
|
102
102
|
* Connections instance for managing authentication connections.
|
|
103
103
|
*/
|
package/src/app/index.ts
CHANGED
|
@@ -28,3 +28,10 @@ export * from './streaming/streamingResponse'
|
|
|
28
28
|
export * from './streaming/citation'
|
|
29
29
|
export * from './teamsAttachmentDownloader'
|
|
30
30
|
export * from './proactive'
|
|
31
|
+
export type {
|
|
32
|
+
RateLimitMessageFactory,
|
|
33
|
+
RateLimitResult,
|
|
34
|
+
RateLimitRule,
|
|
35
|
+
RateLimitScope,
|
|
36
|
+
RateLimitStorageErrorBehavior
|
|
37
|
+
} from './rateLimit'
|
|
@@ -7,7 +7,13 @@ import type { BaseAdapter } from '../../baseAdapter'
|
|
|
7
7
|
import type { TurnContext } from '../../turnContext'
|
|
8
8
|
import type { TurnState } from '../turnState'
|
|
9
9
|
import type { RouteHandler } from '../routeHandler'
|
|
10
|
-
import type {
|
|
10
|
+
import type { StorageV2 } from '../../storage/storage'
|
|
11
|
+
import {
|
|
12
|
+
asStorageV2,
|
|
13
|
+
assertStorageDeleteSucceeded,
|
|
14
|
+
assertStorageWriteSucceeded,
|
|
15
|
+
getStorageReadValue,
|
|
16
|
+
} from '../../storage/storageCompatibility'
|
|
11
17
|
import type { AgentApplication } from '../agentApplication'
|
|
12
18
|
import type { ProactiveOptions } from './proactiveOptions'
|
|
13
19
|
import type { CreateConversationOptions } from './createConversationOptions'
|
|
@@ -41,31 +47,31 @@ export class Proactive<TState extends TurnState> {
|
|
|
41
47
|
|
|
42
48
|
private readonly _app: AgentApplication<TState>
|
|
43
49
|
private readonly _options: ProactiveOptions
|
|
44
|
-
private readonly _storage?:
|
|
50
|
+
private readonly _storage?: StorageV2
|
|
45
51
|
|
|
46
52
|
constructor (app: AgentApplication<TState>, options: ProactiveOptions) {
|
|
47
53
|
this._app = app
|
|
48
54
|
this._options = options
|
|
49
|
-
this._storage = options.storage
|
|
55
|
+
this._storage = options.storage ? asStorageV2(options.storage) : undefined
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
private _storageKey (conversationId: string): string {
|
|
53
59
|
return `${STORAGE_KEY_PREFIX}${conversationId}`
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
private requireStorage ():
|
|
62
|
+
private requireStorage (): StorageV2 {
|
|
57
63
|
if (!this._storage) {
|
|
58
64
|
throw ExceptionHelper.generateException(Error, Errors.ProactiveStorageRequired)
|
|
59
65
|
}
|
|
60
66
|
return this._storage
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
private requireAppStorage ():
|
|
69
|
+
private requireAppStorage (): StorageV2 {
|
|
64
70
|
const storage = this._app.options.storage
|
|
65
71
|
if (!storage) {
|
|
66
72
|
throw ExceptionHelper.generateException(Error, Errors.ProactiveAppStorageRequired)
|
|
67
73
|
}
|
|
68
|
-
return storage
|
|
74
|
+
return asStorageV2(storage)
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
// ---------------------------------------------------------------------------
|
|
@@ -120,7 +126,8 @@ export class Proactive<TState extends TurnState> {
|
|
|
120
126
|
const key = this._storageKey(id)
|
|
121
127
|
record({ conversationId: id })
|
|
122
128
|
const linkedItem = await actions.link(storage, key)
|
|
123
|
-
await storage.write({ [key]: { ...linkedItem, reference: conv.reference, claims: conv.claims } })
|
|
129
|
+
const results = await storage.write({ [key]: { ...linkedItem, reference: conv.reference, claims: conv.claims } })
|
|
130
|
+
assertStorageWriteSucceeded(results, [key])
|
|
124
131
|
return id
|
|
125
132
|
})
|
|
126
133
|
}
|
|
@@ -142,7 +149,8 @@ export class Proactive<TState extends TurnState> {
|
|
|
142
149
|
return trace(ProactiveTraceDefinitions.getConversation, async ({ record }) => {
|
|
143
150
|
record({ conversationId })
|
|
144
151
|
const storageKey = this._storageKey(conversationId)
|
|
145
|
-
const
|
|
152
|
+
const results = await this.requireStorage().read<Conversation>([storageKey])
|
|
153
|
+
const stored = getStorageReadValue(results, storageKey)
|
|
146
154
|
if (!stored) {
|
|
147
155
|
record({ found: false })
|
|
148
156
|
return undefined
|
|
@@ -193,7 +201,9 @@ export class Proactive<TState extends TurnState> {
|
|
|
193
201
|
async deleteConversation (conversationId: string): Promise<void> {
|
|
194
202
|
return trace(ProactiveTraceDefinitions.deleteConversation, async ({ record }) => {
|
|
195
203
|
record({ conversationId })
|
|
196
|
-
|
|
204
|
+
const key = this._storageKey(conversationId)
|
|
205
|
+
const results = await this.requireStorage().delete([key])
|
|
206
|
+
assertStorageDeleteSucceeded(results, [key])
|
|
197
207
|
})
|
|
198
208
|
}
|
|
199
209
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { StorageProvider } from '../../storage/storage'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Configuration for the proactive messaging subsystem.
|
|
@@ -14,7 +14,7 @@ export interface ProactiveOptions {
|
|
|
14
14
|
* A warning is logged when the fallback is used.
|
|
15
15
|
* Throws at initialization time if neither is configured.
|
|
16
16
|
*/
|
|
17
|
-
storage?:
|
|
17
|
+
storage?: StorageProvider
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* When `true` (default), `continueConversation()` throws if any requested
|