Skip to content

Commit 21b96fa

Browse files
aemada-awsdhasani23jsalles
authored
feat(amazonq): enable builder id auth for amazon q (aws#4608)
* feat: enable builder id auth for amazon q * address comment to update if condition * Fix verification of amazonQ connections and adding correct scopes --------- Co-authored-by: David Hasani <[email protected]> Co-authored-by: Joao Salles <[email protected]>
1 parent 4069db1 commit 21b96fa

File tree

6 files changed

+39
-46
lines changed

6 files changed

+39
-46
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Enable Amazon Q feature development and Amazon Q transform capabilities (/dev and /transform) for AWS Builder ID users."
4+
}

packages/core/src/amazonq/explorer/amazonQTreeNode.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ResourceTreeDataProvider, TreeNode } from '../../shared/treeview/resour
99
import { AuthUtil, amazonQScopes, codeWhispererChatScopes } from '../../codewhisperer/util/authUtil'
1010
import { createLearnMoreNode, enableAmazonQNode, switchToAmazonQNode } from './amazonQChildrenNodes'
1111
import { Command, Commands } from '../../shared/vscode/commands2'
12-
import { hasScopes, isSsoConnection } from '../../auth/connection'
12+
import { hasScopes, isBuilderIdConnection, isSsoConnection } from '../../auth/connection'
1313
import { listCodeWhispererCommands } from '../../codewhisperer/ui/statusBarMenu'
1414
import { getIcon } from '../../shared/icons'
1515
import { vsCodeState } from '../../codewhisperer/models/model'
@@ -77,6 +77,16 @@ export class AmazonQNode implements TreeNode {
7777
}
7878
}
7979

80+
if (isBuilderIdConnection(AuthUtil.instance.conn)) {
81+
const missingScopes =
82+
(AuthUtil.instance.isBuilderIdInUse() && !hasScopes(AuthUtil.instance.conn, amazonQScopes)) ||
83+
!hasScopes(AuthUtil.instance.conn, codeWhispererChatScopes)
84+
85+
if (missingScopes) {
86+
return [enableAmazonQNode(), createLearnMoreNode()]
87+
}
88+
}
89+
8090
return [
8191
vsCodeState.isFreeTierLimitReached ? createFreeTierLimitMet('tree') : switchToAmazonQNode('tree'),
8292
createNewMenuButton(),

packages/core/src/codewhisperer/models/constants.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,6 @@ export const transformByQPartiallyCompletedMessage =
310310
export const noPomXmlFoundMessage =
311311
'None of your open Java projects are supported by Amazon Q Code Transformation. Currently, Amazon Q can only upgrade Java projects built on Maven. A pom.xml must be present in the root of your project to upgrade it. For more information, see the [Amazon Q documentation](LINK_HERE).'
312312

313-
export const noActiveIdCMessage =
314-
'Amazon Q Code Transformation requires an active IAM Identity Center connection. For more information, see the [Code Transformation documentation](LINK_HERE).'
315-
316313
export const noOngoingJobMessage = 'No job is in-progress at the moment'
317314

318315
export const jobInProgressMessage = 'Job is already in-progress'

packages/core/src/codewhisperer/util/authUtil.ts

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,15 @@ export const isValidCodeWhispererCoreConnection = (conn?: Connection): conn is C
5757
(isSsoConnection(conn) && hasScopes(conn, codeWhispererCoreScopes))
5858
)
5959
}
60-
/** For Builder ID only, if using IdC then use {@link isValidAmazonQConnection} */
61-
export const isValidCodeWhispererChatConnection = (conn?: Connection): conn is Connection => {
60+
/** Superset that includes all of CodeWhisperer + Amazon Q */
61+
export const isValidAmazonQConnection = (conn?: Connection): conn is Connection => {
6262
return (
63-
isBuilderIdConnection(conn) &&
63+
(isSsoConnection(conn) || isBuilderIdConnection(conn)) &&
6464
isValidCodeWhispererCoreConnection(conn) &&
65-
hasScopes(conn, codeWhispererChatScopes)
65+
hasScopes(conn, amazonQScopes)
6666
)
6767
}
6868

69-
/** Superset that includes all of CodeWhisperer + Amazon Q */
70-
export const isValidAmazonQConnection = (conn?: Connection): conn is Connection => {
71-
return isSsoConnection(conn) && isValidCodeWhispererCoreConnection(conn) && hasScopes(conn, amazonQScopes)
72-
}
73-
7469
interface HasAlreadySeenQWelcome {
7570
local?: boolean
7671
devEnv?: boolean
@@ -217,9 +212,9 @@ export class AuthUtil {
217212
let conn = (await this.auth.listConnections()).find(isBuilderIdConnection)
218213

219214
if (!conn) {
220-
conn = await this.auth.createConnection(createBuilderIdProfile(codeWhispererChatScopes))
221-
} else if (!isValidCodeWhispererChatConnection(conn)) {
222-
conn = await this.secondaryAuth.addScopes(conn, codeWhispererChatScopes)
215+
conn = await this.auth.createConnection(createBuilderIdProfile(amazonQScopes))
216+
} else if (!isValidAmazonQConnection(conn)) {
217+
conn = await this.secondaryAuth.addScopes(conn, amazonQScopes)
223218
}
224219

225220
if (this.auth.getConnectionState(conn) === 'invalid') {
@@ -333,11 +328,10 @@ export class AuthUtil {
333328
// Edge Case: With the addition of Amazon Q/Chat scopes we may need to add
334329
// the new scopes to existing pre-chat connections.
335330
if (addMissingScopes) {
336-
if (isBuilderIdConnection(this.conn) && !isValidCodeWhispererChatConnection(this.conn)) {
337-
const conn = await this.secondaryAuth.addScopes(this.conn, codeWhispererChatScopes)
338-
await this.secondaryAuth.useNewConnection(conn)
339-
return
340-
} else if (isIdcSsoConnection(this.conn) && !isValidAmazonQConnection(this.conn)) {
331+
if (
332+
(isBuilderIdConnection(this.conn) || isIdcSsoConnection(this.conn)) &&
333+
!isValidAmazonQConnection(this.conn)
334+
) {
341335
const conn = await this.secondaryAuth.addScopes(this.conn, amazonQScopes)
342336
await this.secondaryAuth.useNewConnection(conn)
343337
return
@@ -386,7 +380,7 @@ export class AuthUtil {
386380
}
387381

388382
public isValidCodeTransformationAuthUser(): boolean {
389-
return this.isEnterpriseSsoInUse() && this.isConnectionValid()
383+
return (this.isEnterpriseSsoInUse() || this.isBuilderIdInUse()) && this.isConnectionValid()
390384
}
391385
}
392386

@@ -411,23 +405,11 @@ export async function getChatAuthState(cwAuth = AuthUtil.instance): Promise<Feat
411405
// default to expired to indicate reauth is needed if unmodified
412406
const state: FeatureAuthState = buildFeatureAuthState(AuthStates.expired)
413407

414-
if (isBuilderIdConnection(currentConnection)) {
415-
// Regardless, if using Builder ID, Amazon Q is unsupported
416-
state[Features.amazonQ] = AuthStates.unsupported
417-
}
418-
419408
if (cwAuth.isConnectionExpired()) {
420409
return state
421410
}
422411

423-
if (isBuilderIdConnection(currentConnection)) {
424-
if (isValidCodeWhispererCoreConnection(currentConnection)) {
425-
state[Features.codewhispererCore] = AuthStates.connected
426-
}
427-
if (isValidCodeWhispererChatConnection(currentConnection)) {
428-
state[Features.codewhispererChat] = AuthStates.connected
429-
}
430-
} else if (isIdcSsoConnection(currentConnection)) {
412+
if (isBuilderIdConnection(currentConnection) || isIdcSsoConnection(currentConnection)) {
431413
if (isValidCodeWhispererCoreConnection(currentConnection)) {
432414
state[Features.codewhispererCore] = AuthStates.connected
433415
}

packages/core/src/test/codewhisperer/util/authUtil.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('AuthUtil', async function () {
4444
const conn = authUtil.conn
4545
assert.strictEqual(conn?.type, 'sso')
4646
assert.strictEqual(conn.label, 'AWS Builder ID')
47-
assert.deepStrictEqual(conn.scopes, codeWhispererChatScopes)
47+
assert.deepStrictEqual(conn.scopes, amazonQScopes)
4848
})
4949

5050
it('if there IS an existing AwsBuilderID conn, it will upgrade the scopes and use it', async function () {
@@ -61,7 +61,7 @@ describe('AuthUtil', async function () {
6161
const conn = authUtil.conn
6262
assert.strictEqual(conn?.type, 'sso')
6363
assert.strictEqual(conn.id, existingBuilderId.id)
64-
assert.deepStrictEqual(conn.scopes, codeWhispererChatScopes)
64+
assert.deepStrictEqual(conn.scopes, amazonQScopes)
6565
})
6666

6767
it('if there is no valid enterprise SSO conn, will create and use one', async function () {
@@ -155,15 +155,15 @@ describe('AuthUtil', async function () {
155155
assert.deepStrictEqual(authUtil.conn?.scopes, codeWhispererCoreScopes)
156156
})
157157

158-
it('reauthenticate adds missing CodeWhisperer Chat Builder ID scopes when explicitly required', async function () {
158+
it('reauthenticate adds missing Builder ID scopes when explicitly required', async function () {
159159
const conn = await auth.createConnection(createBuilderIdProfile({ scopes: codeWhispererCoreScopes }))
160160
await auth.useConnection(conn)
161161

162162
// method under test
163163
await authUtil.reauthenticate(true)
164164

165165
assert.strictEqual(authUtil.conn?.type, 'sso')
166-
assert.deepStrictEqual(authUtil.conn?.scopes, codeWhispererChatScopes)
166+
assert.deepStrictEqual(authUtil.conn?.scopes, amazonQScopes)
167167
})
168168

169169
it('reauthenticate adds missing Amazon Q IdC scopes when explicitly required', async function () {
@@ -222,7 +222,7 @@ describe('AuthUtil', async function () {
222222
assert.strictEqual(authUtil.conn?.id, upgradeableConn.id)
223223
assert.strictEqual(authUtil.conn.startUrl, upgradeableConn.startUrl)
224224
assert.strictEqual(authUtil.conn.ssoRegion, upgradeableConn.ssoRegion)
225-
assert.deepStrictEqual(authUtil.conn.scopes, codeWhispererChatScopes)
225+
assert.deepStrictEqual(authUtil.conn.scopes, amazonQScopes)
226226
assert.strictEqual((await auth.listConnections()).filter(isAnySsoConnection).length, 1)
227227
})
228228

@@ -277,20 +277,20 @@ describe('getChatAuthState()', function () {
277277
assert.deepStrictEqual(result, {
278278
codewhispererCore: AuthStates.connected,
279279
codewhispererChat: AuthStates.expired,
280-
amazonQ: AuthStates.unsupported,
280+
amazonQ: AuthStates.expired,
281281
})
282282
})
283283

284284
it('indicates all SUPPORTED features connected when all scopes are set', async function () {
285-
const conn = await auth.createConnection(createBuilderIdProfile({ scopes: codeWhispererChatScopes }))
285+
const conn = await auth.createConnection(createBuilderIdProfile({ scopes: amazonQScopes }))
286286
createToken(conn)
287287
await auth.useConnection(conn)
288288

289289
const result = await getChatAuthState(authUtil)
290290
assert.deepStrictEqual(result, {
291291
codewhispererCore: AuthStates.connected,
292292
codewhispererChat: AuthStates.connected,
293-
amazonQ: AuthStates.unsupported,
293+
amazonQ: AuthStates.connected,
294294
})
295295
})
296296

@@ -304,7 +304,7 @@ describe('getChatAuthState()', function () {
304304
assert.deepStrictEqual(result, {
305305
codewhispererCore: AuthStates.expired,
306306
codewhispererChat: AuthStates.expired,
307-
amazonQ: AuthStates.unsupported,
307+
amazonQ: AuthStates.expired,
308308
})
309309
})
310310
})

packages/core/src/testE2E/util/codewhispererUtil.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { isValidCodeWhispererChatConnection } from '../../codewhisperer/util/authUtil'
6+
import { isValidAmazonQConnection } from '../../codewhisperer/util/authUtil'
77
import { Auth } from '../../auth/auth'
88

99
/*
@@ -17,7 +17,7 @@ If user has an expired connection they must reauthenticate prior to running test
1717
*/
1818

1919
async function getValidConnection() {
20-
return (await Auth.instance.listConnections()).find(isValidCodeWhispererChatConnection)
20+
return (await Auth.instance.listConnections()).find(isValidAmazonQConnection)
2121
}
2222

2323
//Returns true if a valid connection is found and set, false if not

0 commit comments

Comments
 (0)