diff --git a/CHANGELOG.md b/CHANGELOG.md index 50df1ae0f..d8d341680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- \#435 - Support for Advanced Identitty Cloud Environment Content Security Policy API +- \#436 - Support for Advanced Identitty Cloud Environment Cookie Domains API +- \#437 - Support for Advanced Identitty Cloud Environment Custom Domains API +- \#438 - Support for Advanced Identitty Cloud Environment Federation Enforcement API +- \#439 - Support for Advanced Identitty Cloud Environment Release API +- \#440 - Support for Advanced Identitty Cloud Environment SSO Cookie API + +### Fixed + +- \#448: Frodo Library now accepts an additional optional boolean param `wait`, which if provided delays the response until an OSGi service event confirms the change has been consumed by the corresponding service or the request times out, to the following `frodo.idm.config` functions: + + - createConfigEntity + - updateConfigEntity + ## [2.0.2] - 2024-08-06 ### Added diff --git a/src/lib/FrodoLib.ts b/src/lib/FrodoLib.ts index ca647ea0c..d8ec9ffd4 100644 --- a/src/lib/FrodoLib.ts +++ b/src/lib/FrodoLib.ts @@ -13,7 +13,23 @@ import AdminFederationOps, { import EnvCertificatesOps, { EnvCertificate, } from '../ops/cloud/EnvCertificatesOps'; +import EnvContentSecurityOps, { + EnvContentSecurityPolicy, +} from '../ops/cloud/EnvContentSecurityPolicyOps'; +import EnvCookieDomainsOps, { + EnvCookieDomains, +} from '../ops/cloud/EnvCookieDomainsOps'; import EnvCSRsOps, { EnvCSR } from '../ops/cloud/EnvCSRsOps'; +import EnvCustomDomainsOps, { + EnvCustomDomains, +} from '../ops/cloud/EnvCustomDomainsOps'; +import EnvFederationEnforcementOps, { + EnvFederationEnforcement, +} from '../ops/cloud/EnvFederationEnforcementOps'; +import EnvReleaseOps, { EnvRelease } from '../ops/cloud/EnvReleaseOps'; +import EnvSSOCookieConfigOps, { + EnvSSOCookieConfig, +} from '../ops/cloud/EnvSSOCookieConfigOps'; import EsvCountOps, { EsvCount } from '../ops/cloud/EsvCountOps'; import FeatureOps, { Feature } from '../ops/cloud/FeatureOps'; import LogOps, { Log } from '../ops/cloud/LogOps'; @@ -93,10 +109,15 @@ export type Frodo = { cloud: { adminFed: AdminFederation; - env: { - cert: EnvCertificate; - csr: EnvCSR; - }; + env: EnvContentSecurityPolicy & + EnvCookieDomains & + EnvCustomDomains & + EnvFederationEnforcement & + EnvRelease & + EnvSSOCookieConfig & { + cert: EnvCertificate; + csr: EnvCSR; + }; esvCount: EsvCount; feature: Feature; log: Log; @@ -242,6 +263,12 @@ const FrodoLib = (config: StateInterface = {}): Frodo => { cloud: { adminFed: AdminFederationOps(state), env: { + ...EnvContentSecurityOps(state), + ...EnvCookieDomainsOps(state), + ...EnvCustomDomainsOps(state), + ...EnvFederationEnforcementOps(state), + ...EnvReleaseOps(state), + ...EnvSSOCookieConfigOps(state), cert: EnvCertificatesOps(state), csr: EnvCSRsOps(state), }, diff --git a/src/ops/AuthenticateOps.ts b/src/ops/AuthenticateOps.ts index 450447b86..f861f566d 100644 --- a/src/ops/AuthenticateOps.ts +++ b/src/ops/AuthenticateOps.ts @@ -107,9 +107,29 @@ export default (state: State): Authenticate => { const adminClientPassword = 'doesnotmatter'; const redirectUrlTemplate = '/platform/appAuthHelperRedirect.html'; -const cloudIdmAdminScopes = 'openid fr:idm:* fr:idc:esv:*'; -const forgeopsIdmAdminScopes = 'openid fr:idm:*'; -const serviceAccountDefaultScopes = SERVICE_ACCOUNT_DEFAULT_SCOPES.join(' '); //'fr:am:* fr:idm:* fr:idc:esv:*'; +const s = Constants.AVAILABLE_SCOPES; +const CLOUD_ADMIN_DEFAULT_SCOPES: string[] = [ + s.AnalyticsFullScope, + s.AutoAccessFullScope, + s.CertificateFullScope, + s.ContentSecurityPolicyFullScope, + s.CookieDomainsFullScope, + s.CustomDomainFullScope, + s.ESVFullScope, + s.FederationEnforcementFullScope, + s.IdmFullScope, + s.IGAFullScope, + s.OpenIdScope, + s.PromotionScope, + s.ReleaseFullScope, + s.SSOCookieFullScope, + s.WafFullScope, +]; +const FORGEOPS_ADMIN_DEFAULT_SCOPES: string[] = [s.IdmFullScope, s.OpenIdScope]; + +const cloudAdminScopes = CLOUD_ADMIN_DEFAULT_SCOPES.join(' '); +const forgeopsAdminScopes = FORGEOPS_ADMIN_DEFAULT_SCOPES.join(' '); +const serviceAccountDefaultScopes = SERVICE_ACCOUNT_DEFAULT_SCOPES.join(' '); const fidcClientId = 'idmAdminClient'; const forgeopsClientId = 'idm-admin-ui'; @@ -120,7 +140,7 @@ let adminClientId = fidcClientId; * @param {State} state library state * @returns {string} cookie name */ -async function determineCookieName(state: State) { +async function determineCookieName(state: State): Promise { const data = await getServerInfo({ state }); debugMessage({ message: `AuthenticateOps.determineCookieName: cookieName=${data.cookieName}`, @@ -309,7 +329,7 @@ async function determineDeploymentType(state: State): Promise { [state.getCookieName()]: state.getCookieValue(), }, }; - let bodyFormData = `redirect_uri=${redirectURL}&scope=${cloudIdmAdminScopes}&response_type=code&client_id=${fidcClientId}&csrf=${cookieValue}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`; + let bodyFormData = `redirect_uri=${redirectURL}&scope=${cloudAdminScopes}&response_type=code&client_id=${fidcClientId}&csrf=${cookieValue}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`; deploymentType = Constants.CLASSIC_DEPLOYMENT_TYPE_KEY; try { @@ -332,7 +352,7 @@ async function determineDeploymentType(state: State): Promise { deploymentType = Constants.CLOUD_DEPLOYMENT_TYPE_KEY; } else { try { - bodyFormData = `redirect_uri=${redirectURL}&scope=${forgeopsIdmAdminScopes}&response_type=code&client_id=${forgeopsClientId}&csrf=${state.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`; + bodyFormData = `redirect_uri=${redirectURL}&scope=${forgeopsAdminScopes}&response_type=code&client_id=${forgeopsClientId}&csrf=${state.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`; await authorize({ amBaseUrl: state.getHost(), data: bodyFormData, @@ -524,8 +544,8 @@ async function getAuthCode( try { const bodyFormData = `redirect_uri=${redirectURL}&scope=${ state.getDeploymentType() === Constants.CLOUD_DEPLOYMENT_TYPE_KEY - ? cloudIdmAdminScopes - : forgeopsIdmAdminScopes + ? cloudAdminScopes + : forgeopsAdminScopes }&response_type=code&client_id=${adminClientId}&csrf=${state.getCookieValue()}&decision=allow&code_challenge=${codeChallenge}&code_challenge_method=${codeChallengeMethod}`; const config = { headers: { @@ -934,7 +954,7 @@ export type Tokens = { * @returns {Promise} object containing the tokens */ export async function getTokens({ - forceLoginAsUser = false, + forceLoginAsUser = process.env.FRODO_FORCE_LOGIN_AS_USER ? true : false, autoRefresh = true, types = Constants.DEPLOYMENT_TYPES, callbackHandler = null, diff --git a/src/ops/cloud/ServiceAccountOps.ts b/src/ops/cloud/ServiceAccountOps.ts index 175ac883d..6f6b2e443 100644 --- a/src/ops/cloud/ServiceAccountOps.ts +++ b/src/ops/cloud/ServiceAccountOps.ts @@ -3,6 +3,7 @@ import { createManagedObject, getManagedObject, } from '../../api/ManagedObjectApi'; +import Constants from '../../shared/Constants'; import { State } from '../../shared/State'; import { debugMessage } from '../../utils/Console'; import { FrodoError } from '../FrodoError'; @@ -88,86 +89,46 @@ export default (state: State): ServiceAccount => { const moType = 'svcacct'; -// Scopes -const scopes = { - OpenIdScope: 'openid', - ProfileScope: 'profile', - AmFullScope: 'fr:am:*', - IdmFullScope: 'fr:idm:*', - AutoAccessFullScope: 'fr:autoaccess:*', - IGAFullScope: 'fr:iga:*', - AnalyticsFullScope: 'fr:idc:analytics:*', - - // AMIntrospectRealmTokenScope lets you introspect scopes _from the same realm_, there is a separate scope to introspect tokens from _all_ realms - AMIntrospectRealmTokenScope: 'am-introspect-all-tokens', - - // Special AM scopes (used by resource servers) - AMIntrospectAllTokens: 'am-introspect-all-tokens', - AMIntrospectAllTokensAnyRealm: 'am-introspect-all-tokens-any-realm', - - // Certificate scopes - CertificateFullScope: 'fr:idc:certificate:*', - CertificateReadScope: 'fr:idc:certificate:read', - - // ESV API scopes - ESVFullScope: 'fr:idc:esv:*', - ESVReadScope: 'fr:idc:esv:read', - ESVUpdateScope: 'fr:idc:esv:update', - ESVRestartScope: 'fr:idc:esv:restart', - - // Content security policy scopes - ContentSecurityPolicyFullScope: 'fr:idc:content-security-policy:*', - - // Federation scopes - FederationFullScope: 'fr:idc:federation:*', - FederationReadScope: 'fr:idc:federation:read', - - // Release scopes - ReleaseFullScope: 'fr:idc:release:*', - - // SSOCookie scopes - SSOCookieFullScope: 'fr:idc:sso-cookie:*', - - // CustomDomainFullScope Custom domain scopes - CustomDomainFullScope: 'fr:idc:custom-domain:*', - - // Promotion scopes - PromotionScope: 'fr:idc:promotion:*', -}; +const s = Constants.AVAILABLE_SCOPES; export const SERVICE_ACCOUNT_ALLOWED_SCOPES: string[] = [ - scopes.AmFullScope, - scopes.AnalyticsFullScope, - scopes.AutoAccessFullScope, - scopes.CertificateFullScope, - scopes.CertificateReadScope, - scopes.ContentSecurityPolicyFullScope, - scopes.CustomDomainFullScope, - scopes.ESVFullScope, - scopes.ESVReadScope, - scopes.ESVRestartScope, - scopes.ESVUpdateScope, - scopes.IdmFullScope, - scopes.IGAFullScope, - scopes.PromotionScope, - scopes.ReleaseFullScope, - scopes.SSOCookieFullScope, + s.AmFullScope, + s.AnalyticsFullScope, + s.AutoAccessFullScope, + s.CertificateFullScope, + s.CertificateReadScope, + s.ContentSecurityPolicyFullScope, + s.CustomDomainFullScope, + s.ESVFullScope, + s.ESVReadScope, + s.ESVRestartScope, + s.ESVUpdateScope, + s.IdmFullScope, + s.IGAFullScope, + s.PromotionScope, + s.ReleaseFullScope, + s.SSOCookieFullScope, + s.WafFullScope, + s.WafReadScope, + s.WafWriteScope, + s.CookieDomainsFullScope, ]; export const SERVICE_ACCOUNT_DEFAULT_SCOPES: string[] = [ - scopes.AmFullScope, - scopes.AnalyticsFullScope, - scopes.AutoAccessFullScope, - scopes.CertificateFullScope, - scopes.CertificateReadScope, - scopes.ContentSecurityPolicyFullScope, - scopes.CustomDomainFullScope, - scopes.ESVFullScope, - scopes.IdmFullScope, - scopes.IGAFullScope, - scopes.PromotionScope, - scopes.ReleaseFullScope, - scopes.SSOCookieFullScope, + s.AmFullScope, + s.AnalyticsFullScope, + s.AutoAccessFullScope, + s.CertificateFullScope, + s.ContentSecurityPolicyFullScope, + s.CookieDomainsFullScope, + s.CustomDomainFullScope, + s.ESVFullScope, + s.IdmFullScope, + s.IGAFullScope, + s.PromotionScope, + s.ReleaseFullScope, + s.SSOCookieFullScope, + s.WafFullScope, ]; export type ServiceAccountType = IdObjectSkeletonInterface & { diff --git a/src/shared/Constants.ts b/src/shared/Constants.ts index 9dab32fde..7643c7c2f 100644 --- a/src/shared/Constants.ts +++ b/src/shared/Constants.ts @@ -33,6 +33,62 @@ const FRODO_CONNECTION_PROFILES_PATH_KEY = 'FRODO_CONNECTION_PROFILES_PATH'; const FRODO_MASTER_KEY_PATH_KEY = 'FRODO_MASTER_KEY_PATH'; const FRODO_MASTER_KEY_KEY = 'FRODO_MASTER_KEY'; const FRODO_TOKEN_CACHE_PATH_KEY = 'FRODO_TOKEN_CACHE_PATH'; +const AVAILABLE_SCOPES = { + OpenIdScope: 'openid', + ProfileScope: 'profile', + AmFullScope: 'fr:am:*', + IdmFullScope: 'fr:idm:*', + AutoAccessFullScope: 'fr:autoaccess:*', + IGAFullScope: 'fr:iga:*', + AnalyticsFullScope: 'fr:idc:analytics:*', + + // AMIntrospectRealmTokenScope lets you introspect scopes _from the same realm_, there is a separate scope to introspect tokens from _all_ realms + AMIntrospectRealmTokenScope: 'am-introspect-all-tokens', + + // Special AM scopes (used by resource servers) + AMIntrospectAllTokens: 'am-introspect-all-tokens', + AMIntrospectAllTokensAnyRealm: 'am-introspect-all-tokens-any-realm', + + // Certificate scopes + CertificateFullScope: 'fr:idc:certificate:*', + CertificateReadScope: 'fr:idc:certificate:read', + + // ESV API scopes + ESVFullScope: 'fr:idc:esv:*', + ESVReadScope: 'fr:idc:esv:read', + ESVUpdateScope: 'fr:idc:esv:update', + ESVRestartScope: 'fr:idc:esv:restart', + + // Content security policy scopes + ContentSecurityPolicyFullScope: 'fr:idc:content-security-policy:*', + + // Federation scopes + FederationFullScope: 'fr:idc:federation:*', + FederationReadScope: 'fr:idc:federation:read', + + // Release scopes + ReleaseFullScope: 'fr:idc:release:*', + + // SSOCookie scopes + SSOCookieFullScope: 'fr:idc:sso-cookie:*', + + // CustomDomainFullScope Custom domain scopes + CustomDomainFullScope: 'fr:idc:custom-domain:*', + + // Promotion scopes + PromotionScope: 'fr:idc:promotion:*', + + // WAF scopes + WafFullScope: 'fr:idc:advanced-gateway:*', + WafReadScope: 'fr:idc:advanced-gateway:read', + WafWriteScope: 'fr:idc:advanced-gateway:write', + + // Cookie Domains scopes + CookieDomainsFullScope: 'fr:idc:cookie-domain:*', + + // Admin Federation Enforcement + FederationEnforcementFullScope: 'fr:idc:federation:*', +}; export default { DEFAULT_REALM_KEY, @@ -46,4 +102,5 @@ export default { FRODO_MASTER_KEY_PATH_KEY, FRODO_MASTER_KEY_KEY, FRODO_TOKEN_CACHE_PATH_KEY, + AVAILABLE_SCOPES, }; diff --git a/src/utils/AutoSetupPolly.ts b/src/utils/AutoSetupPolly.ts index 477a36a04..e0d78556e 100644 --- a/src/utils/AutoSetupPolly.ts +++ b/src/utils/AutoSetupPolly.ts @@ -33,7 +33,11 @@ switch (process.env.FRODO_POLLY_MODE) { // record mock responses from a real env: `npm run test:record` case 'record': { setDefaultState(); - if (!(await getTokens({ forceLoginAsUser: false, state }))) + if ( + !(await getTokens({ + state, + })) + ) throw new Error( `Unable to record mock responses from '${state.getHost()}'` );