Skip to main content

Ideas

Filter by idea status

10000+ Ideas

Ability to mass merge or mass delete tags without needing to view WorkspacesNew

DescriptionI would like the ability for Adobe Analytics admins to mass merge or mass delete Workspace tags and component tags without needing to view the affected Workspace dashboards or affected components themselves. There should also be an ability for us to rename tags so they are either all capitalized the same way - e.g., Sentence case, Title Case, or lowercase. At the moment, people at my company are able to create tags on their own, which means that if there are typos to a tag, or if there are duplicates, we need to go into each Workspace dashboard or each component itself for us to correct it. It makes it very difficult to find the Workspace dashboard we need. Tags then become an unused feature for us on Adobe Analytics, and we just end up using the search bar, which is not always effective. I have attached some screenshots to help visualize the problem.Why is this feature important to youTo make it easier for users to search for the Workspace they need without needing to rely on the Workspace listing search bar, which is not always effective, because it pulls up a large number of Workspaces (including drafts, finalized copies, and QA test boards).How would you like the feature to workThere should be a feature accessible to all admins or people with admin permissions where we can easily mass merge or mass delete Workspace tags (for both the dashboards and the components) without needing to click to adjust each one manually. Current BehaviourCurrently, for us to delete and merge tags, we need to slowly go through each tag, mass select the dashboards or components, remove the unneeded tag, and redo this entire process until there is only one tag left. This is incredibly tedious. We are also not able to edit tag capitalizations at all. 

A secret slot pattern for runtime-configurable REST integrations on AEMaaCSNew

Applies to: AEM as a Cloud Service (author and publish)I ran into this while building a generic REST connector. You import an OpenAPI spec, authors set up a connector in /conf, and the data ends up in Content Fragments. The data side was the easy part. Credentials were not. Every API needs a secret, and on AEMaaCS a secret only reaches your code through OSGi config that ships with a deployment. So "an author adds a new API" quietly becomes "a developer ships a release", which defeats the point of a generic connector.This is the idea I came up with for that, and I'd like to know what you think of it.A quick word on where it stands. It's my own design on top of what Adobe documents, not an Adobe recommendation. I've compiled and unit tested the service class on a plain JDK, but I haven't run any of it on a real AEMaaCS environment. Adobe documents the local setup for placeholders (it uses the Felix configadmin plugin) but not how the cloud runtime resolves them, so wherever I lean on the plugin's behavior I'm going by its README and source. Worth checking on a dev environment before you trust it.The ideaInstead of one secret per integration, deploy a pool of numbered slots once: REST_SECRET_01, REST_SECRET_02 and so on. Each slot is a $[secret:...] placeholder in an OSGi config, sitting next to the hosts it's allowed to be sent to. The connector config in /conf only stores the slot name, for example "secretRef": "REST_SECRET_01". A small OSGi service looks the slot up when you make the call, and it only hands back the value if the URL points at one of that slot's hosts.For a host that's already declared, an author then needs a Cloud Manager variable and a connector config, with no code release. A brand-new host still needs a config change in Git. I'm fine with that, because the host list is the thing that stops a credential going somewhere it shouldn't.Declaring the slotsThis goes in your ui.config module. I only declare slots under config.author, since the sync jobs run there and publish is the internet-facing tier. Name the file after the component's class, so in my case com.example.restconnector.core.secrets.SecretSlotService.cfg.json:{ "REST_SECRET_01": "$[secret:REST_SECRET_01]", "REST_SECRET_01.hosts": ["api.example-weather.com"], "REST_SECRET_02": "$[secret:REST_SECRET_02]", "REST_SECRET_02.hosts": ["auth.example-stores.com"], "REST_SECRET_03": "$[secret:REST_SECRET_03]", "REST_SECRET_03.hosts": ["auth.example-stores.com"]}Why hosts per slot and not one global allowlist? With a single list, an author editing a connector could pair a token meant for API A with API B's host and send it to the wrong place. Per-slot hosts close that gap. They don't help against someone who can deploy code, since code can read any slot.Two things to keep in mind. Preview takes its OSGi config from publish (there's no config.preview folder), and config is resolved per PID from the most specific runmode folder, so you can't spread one PID across config.author and config.author.dev. Also, I set configurationPolicy = REQUIRE, which means the service doesn't exist on a tier with no config for it. The OSGi spec says a required configuration has to be present for the component to be satisfied. Anything on publish that references the service needs an optional reference.The serviceIt's one DS component. It keeps an immutable map of slots, swaps in a new one whenever the config changes, and its activate method takes a Map<String, Object>, the same as the secrets manager in Adobe's tutorial. getSecret() is the only method that returns a value, and this is the check that decides:if (!"https".equalsIgnoreCase(target.getScheme()) || target.getUserInfo() != null) { return Optional.empty();}if (target.getPort() != -1 && target.getPort() != 443) { return Optional.empty();}String host = normalizeHost(target.getHost());if (host == null || !slot.hosts.contains(host)) { return Optional.empty();}return Optional.of(slot.value);A slot counts as unset if the value is missing, empty, still the literal placeholder, or has a line break in it, so isSet() and getSecret() can never disagree. Host names get normalized on both sides (lowercase, punycode, no trailing dot) and have to be plain names, so no wildcards, paths, schemes, ports, underscores or IP literals. When an entry gets dropped, or a slot has a value but no usable host, it logs a warning, so a typo doesn't fail silently. A Unicode host in the URL won't match, so use the punycode form, because java.net.URI gives no host for non-ASCII names. It never logs a value. isDeclared() and isSet() are there for a status page.You need Java 10 or later because of Map.copyOf(). Cloud Manager builds with JDK 8 unless you set .cloudmanager/java-version, and Adobe's build page says that file should contain only 21 or 17.Using itBuild one URI, check the secret against it, and pass that same URI to the client, so what you checked is exactly what you call:URI target = URI.create(url);String token = secretSlotService.getSecret(connector.getSecretRef(), target) .orElseThrow(() -> new ConnectorException( "Secret slot " + connector.getSecretRef() + " is unavailable for this request"));HttpGet request = new HttpGet(target);request.setHeader("Authorization", "Bearer " + token);I build the HttpClient once on activate and close it on deactivate, with redirects off so a redirect can't carry the header to another host:client = HttpClientBuilder.create() .useSystemProperties() .setDefaultRequestConfig(RequestConfig.custom() .setRedirectsEnabled(false) .setConnectTimeout(1000) .setSocketTimeout(5000) .setConnectionRequestTimeout(1000) .build()) .build();The 1 second connect and 5 second read timeouts are what Adobe's development guidelines suggest. useSystemProperties() matters if you use a dedicated egress IP or VPN, because Apache HttpClient needs it to use the proxy. Whatever you do, keep the secret, the headers and any URL with credentials out of error messages and logs, and don't turn off TLS hostname verification.If an API needs two values, like a client ID and a secret, use one slot per value. Those slots only list the token host, and the access token you get back stays in memory.Then in Cloud Manager, create a secret variable named exactly like the slot, pick the service it applies to, and paste the value without leading or trailing spaces. Save once for all the variables you're changing.What to watch out forAdobe's docs don't quite agree on how a change is applied. The OSGi page says calling the API deploys new variables like a code deployment, and that author and publish restart, typically within a few minutes, without the quality gates. The environment variables page says changes take effect without code changes or deployments, and shows an Updating status. A KB article about preview-scoped variables mentions only Preview restarting, and nothing says what an author-scoped variable does. I'd test it on dev and schedule changes outside editing hours. If a variable sits on Updating for more than 10 to 15 minutes, KA-36282 says to look for stray whitespace in the name or value.On limits: names are 2 to 100 characters matching [a-zA-Z_][a-zA-Z_0-9]*, and INTERNAL_, ADOBE_ and CONST_ are reserved. Values can be 2048 characters, but the API reference says secretString values must be under 500. The number of variables per environment is 200 on the OSGi page (the 2020.7.0 release notes say it was raised to 200) and 400 on the environment variables page. I plan for 200. A pool of 20 slots uses 40 if each slot is set separately for author and publish.If no value is set, Adobe says the placeholder stays in place at runtime, so your code sees the literal $[secret:...] text. That's why the service treats it as unset. Placeholders only work in OSGi config. According to the Felix plugin README, a DS component gets resolved values, but code that reads ConfigurationAdmin properties directly sees the raw placeholders.For outbound calls, ports 80 and 443 are open by default. Other HTTP(S) ports need one of the advanced networking options and the documented proxy, and that page also recommends connection pooling.Anyone who can open the OSGi Components view in the Developer Console may see the injected values. Adobe doesn't say whether secret variables are masked there, so I'd assume they aren't, and I'd limit who has console access. To check whether a slot has a value, use your own isSet() status page rather than the console.This isn't meant for OAuth refresh tokens or anything else that changes at runtime, for credentials of around 500 characters or more (PEM keys, say), for big pools, or for changes that must take effect instantly, since every secret change is an environment update.A few habits I'd keep. Never log values, the activation property map or request headers. Restrict write access to connector configs under /conf, because anyone who can edit one can choose any slot. Use separate secrets per environment. Rotate by changing the value in Cloud Manager, which Adobe's tutorial says needs no redeployment, but values aren't versioned, so a code rollback can pick up a newer value. For risky changes, put the new value in a spare slot and repoint the reference. And when you replace a placeholder with a hardcoded value, delete the old variable too, because KA-27437 says AEM keeps preferring the existing secret until you do.Testing locallyAdd this to the end of crx-quickstart/conf/sling.properties and restart:org.apache.felix.configadmin.plugin.interpolation.secretsdir=${sling.home}/secretsdirThen create one file per secret in that folder, named exactly like the variable, with no extension. Adobe's OSGi page says all the secret files go in the same directory. Write each file without a trailing newline, for example printf '%s' 'value' > secretsdir/REST_SECRET_01. The plugin source trims whitespace, but its README describes the file contents as taken as-is, and a trailing newline would make the slot count as unset in this service.A Tools page for empty slots (idea for later)When several sites each keep their own connector configs, people need a way to see which configurations are still waiting for a value. I'm thinking of a small AEM tool, maybe under Tools > REST connector > Secret slot status, that lists each connector configuration, shows whether its slots are set, empty, unknown or missing, and names the Cloud Manager variable to create. It would only use isDeclared() and isSet(), so it never sees a secret value. That's just an idea for now, and I'll add details to this post later.That's where I am. If you've solved this a different way, or you can see a hole in the host binding, I'd like to hear about it.Docs I usedManaging secrets in AEM as a Cloud Service Configuring OSGi for AEM as a Cloud Service Environment variables in Cloud Manager Cloud Manager API reference Cloud Manager release notes 2020.7.0 Build environment of Cloud Manager AEM as a Cloud Service Developer Console AEM as a Cloud Service development guidelines Configure advanced networking KA-29983, preview-scoped environment variables KA-36282, variables stuck in Updating KA-27437, stale environment variables OSGi Compendium: ConfigurationPolicy Felix interpolation plugin README

Ability for Account Profiles to Merge Across Multiple Primary & External Source KeysNew

With our consolidated B2C, B2B & B2P instance/sandbox currently in production which includes Salesforce and Marketo account data via the native Marketo connector as well as batch ETL ingestion from our datalake. The result of this is duplicate account profiles with multiple/different primary & external source keys. Currently we have two options to manually correct for these duplicate account profiles: Option 1: Fix the Mapping Change the mapping on the Marketo side so that the “primary key” field (account key) for accounts brought in from Marketo uses the same format as the Salesforce/account data brought in via other sources. Example: If Salesforce/S3 uses 001xxxx...@salescloud.redhat as the account key, configure Marketo’s field mapping to use the same convention instead of the out-of-the-box 001xxxx...@<OrgId>.salesforce. This aligns the account keys from Marketo and Salesforce/S3, allowing AEP merge policies to recognize them as the same account and unite/stitch all attributes and relationships into a single unified profile. Option 2: Add a Shared Identity Field  Create an additional identity field on the account schema (e.g., b2bAccountSharedId) and populate it with a value that both Marketo and Salesforce/S3 can map - so both sources submit a common ID in a new field, marked as an identity. You then configure this field as an additional identity namespace in AEP, allowing merge policies to stitch on either the classic account key or the new shared ID. Accounts originating from either source will have a shared secondary identity that can be stitched, even if the original (primary) field isn’t harmonized, and without disrupting existing integrations. It would be ideal for the ability for duplicate account profiles to unify and/or merge across multiple primary and external keys instead of the manual correction/workaround options.