@@ -10,6 +10,7 @@ import {
1010 classifyMicrosoftDataverseCredentialEnvironment ,
1111 extractMicrosoftDataverseEnvironmentUrl ,
1212 getBoundMicrosoftDataverseEnvironment ,
13+ getMicrosoftDataverseIdentityScopes ,
1314 getMicrosoftDataverseOAuthScopes ,
1415 getMicrosoftDataverseRequiredScope ,
1516 normalizeMicrosoftDataverseEnvironmentUrl ,
@@ -69,6 +70,15 @@ describe('Microsoft Dataverse OAuth environment binding', () => {
6970 ] )
7071 } )
7172
73+ it ( 'derives identity permissions from the canonical service grant' , ( ) => {
74+ expect ( getMicrosoftDataverseIdentityScopes ( LEGACY_DATAVERSE_SCOPES ) ) . toEqual ( [
75+ 'openid' ,
76+ 'profile' ,
77+ 'email' ,
78+ 'offline_access' ,
79+ ] )
80+ } )
81+
7282 it ( 'round-trips the flow-bound environment through absolute and relative callback URLs' , ( ) => {
7383 const absolute = bindMicrosoftDataverseEnvironmentToOAuthCallback (
7484 'https://sim.test/workspace?existing=1' ,
@@ -126,7 +136,7 @@ describe('Microsoft Dataverse OAuth environment binding', () => {
126136 undefined ,
127137 LEGACY_DATAVERSE_SCOPES
128138 )
129- ) . not . toThrow ( )
139+ ) . toThrow ( 'exact legacy scopes' )
130140 expect ( ( ) =>
131141 assertMicrosoftDataverseOAuthLinkRequest (
132142 'https://sim.test/workspace' ,
@@ -283,6 +293,19 @@ describe('Microsoft Dataverse OAuth environment binding', () => {
283293 ) . toThrow ( 'multiple environment scopes' )
284294 } )
285295
296+ it ( 'rejects a malformed internal environment marker instead of treating it as legacy' , ( ) => {
297+ const malformedMarker = '__sim_dataverse_instance__:https://evil.example'
298+ expect ( ( ) => extractMicrosoftDataverseEnvironmentUrl ( [ malformedMarker ] ) ) . toThrow (
299+ 'invalid environment scope'
300+ )
301+ expect (
302+ classifyMicrosoftDataverseCredentialEnvironment (
303+ [ malformedMarker ] ,
304+ 'https://dev.crm.dynamics.com'
305+ )
306+ ) . toBe ( 'invalid' )
307+ } )
308+
286309 it ( 'classifies matching, legacy, different, and ambiguous stored grants' , ( ) => {
287310 const requested = 'https://dev.crm.dynamics.com'
288311 expect (
@@ -331,6 +354,14 @@ describe('Microsoft Dataverse OAuth environment binding', () => {
331354 expect ( prod . id ) . toContain ( ':prod.api.crm.dynamics.com-' )
332355 } )
333356
357+ it ( 'fails closed when the generated account ID invariant is missing' , ( ) => {
358+ expect ( ( ) =>
359+ bindMicrosoftDataverseEnvironmentToUserInfo ( { id : 'entra-user-id' } , [
360+ getMicrosoftDataverseRequiredScope ( 'https://dev.crm.dynamics.com' ) ,
361+ ] )
362+ ) . toThrow ( 'user ID is missing its generated suffix' )
363+ } )
364+
334365 it ( 'rejects callback tokens without an environment audience' , ( ) => {
335366 expect ( ( ) =>
336367 bindMicrosoftDataverseEnvironmentToUserInfo ( userInfoFor ( 'entra-user-id' ) , [
0 commit comments