diff --git a/packages/core/src/routes/interaction/consent/utils.ts b/packages/core/src/routes/interaction/consent/utils.ts index f8f258681..166be829a 100644 --- a/packages/core/src/routes/interaction/consent/utils.ts +++ b/packages/core/src/routes/interaction/consent/utils.ts @@ -5,6 +5,7 @@ import { errors } from 'oidc-provider'; import { filterResourceScopesForTheThirdPartyApplication, findResourceScopes, + isThirdPartyApplication, } from '#src/oidc/resource.js'; import type Libraries from '#src/tenants/Libraries.js'; import type Queries from '#src/tenants/Queries.js'; @@ -118,19 +119,23 @@ export const filterAndParseMissingResourceScopes = async ({ organizationId, }); + const isThirdPartyApp = await isThirdPartyApplication(queries, applicationId); + // Filter the scopes for the third-party application. // Although the "missingResourceScopes" from the prompt details are already filtered, // there may be duplicated scopes from either resources or organization resources. - const filteredScopes = await filterResourceScopesForTheThirdPartyApplication( - libraries, - applicationId, - resourceIndicator, - scopes, - { - includeOrganizationResourceScopes: Boolean(organizationId), - includeResourceScopes: !organizationId, - } - ); + const filteredScopes = isThirdPartyApp + ? await filterResourceScopesForTheThirdPartyApplication( + libraries, + applicationId, + resourceIndicator, + scopes, + { + includeOrganizationResourceScopes: Boolean(organizationId), + includeResourceScopes: !organizationId, + } + ) + : scopes; return [ resourceIndicator, diff --git a/packages/integration-tests/src/tests/api/interaction/third-party-sign-in/happy-path.test.ts b/packages/integration-tests/src/tests/api/interaction/consent/happy-path.test.ts similarity index 87% rename from packages/integration-tests/src/tests/api/interaction/third-party-sign-in/happy-path.test.ts rename to packages/integration-tests/src/tests/api/interaction/consent/happy-path.test.ts index 1d9ebea31..fc51bcb6b 100644 --- a/packages/integration-tests/src/tests/api/interaction/third-party-sign-in/happy-path.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/consent/happy-path.test.ts @@ -8,6 +8,7 @@ import { createApplication, deleteApplication } from '#src/api/application.js'; import { consent, getConsentInfo, putInteraction } from '#src/api/interaction.js'; import { OrganizationScopeApi } from '#src/api/organization-scope.js'; import { createResource, deleteResource } from '#src/api/resource.js'; +import { assignUsersToRole, createRole, deleteRole } from '#src/api/role.js'; import { createScope } from '#src/api/scope.js'; import { initClient } from '#src/helpers/client.js'; import { OrganizationApiTest, OrganizationRoleApiTest } from '#src/helpers/organization.js'; @@ -24,6 +25,7 @@ import { describe('consent api', () => { const applications = new Map(); const thirdPartyApplicationName = 'consent-third-party-app'; + const firstPartyApplicationName = 'consent-first-party-app'; const redirectUri = 'http://example.com'; const bootStrapApplication = async () => { @@ -39,7 +41,20 @@ describe('consent api', () => { } ); + const firstPartyApplication = await createApplication( + firstPartyApplicationName, + ApplicationType.Traditional, + { + isThirdParty: false, + oidcClientMetadata: { + redirectUris: [redirectUri], + postLogoutRedirectUris: [], + }, + } + ); + applications.set(thirdPartyApplicationName, thirdPartyApplication); + applications.set(firstPartyApplicationName, firstPartyApplication); await assignUserConsentScopes(thirdPartyApplication.id, { userScopes: [UserScope.Profile], @@ -253,6 +268,44 @@ describe('consent api', () => { }); describe('submit consent info', () => { + it('should not affect first party app', async () => { + const application = applications.get(firstPartyApplicationName); + assert(application, new Error('application.not_found')); + + const { userProfile, user } = await generateNewUser({ username: true, password: true }); + const resource = await createResource(generateResourceName(), generateResourceIndicator()); + const scope = await createScope(resource.id, generateScopeName()); + const role = await createRole({ name: generateRoleName(), scopeIds: [scope.id] }); + await assignUsersToRole([user.id], role.id); + + const client = await initClient( + { + appId: application.id, + appSecret: application.secret, + // First party app should block the scope, even though it is not assigned to the app + scopes: [UserScope.Profile, scope.name], + resources: [resource.indicator], + }, + redirectUri + ); + + await client.successSend(putInteraction, { + event: InteractionEvent.SignIn, + identifier: { + username: userProfile.username, + password: userProfile.password, + }, + }); + + const { redirectTo } = await client.submitInteraction(); + + await client.processSession(redirectTo); + + await deleteResource(resource.id); + await deleteUser(user.id); + await deleteRole(role.id); + }); + it('should perform manual consent successfully', async () => { const application = applications.get(thirdPartyApplicationName); assert(application, new Error('application.not_found'));