Subaccount Roles Import
Assign Neo application roles and group memberships into CF role collections after app deployment.
Purpose
This skill reads the structured roles export produced by subaccount-roles-export and completes the authorization setup in CF by:
- Resolving the live XSUAA
appIdfor each deployed application (e.g.myapp!t1234) - Adding the corresponding role-templates from each deployed XSUAA app to its role collections
- Assigning users to role collections via the BTP CLI
- Assigning group members to group role collections
- Generating a manual checklist for unresolvable apps, group→role links, and the Everyone role
Must run after all applications are deployed. This skill requires live XSUAA apps to exist in CF — the
appId(e.g.myapp!t1234) is only available after the firstcf deploy. Role collections are created byauthentication-xsuaaviaxs-security.json+ deployment; this skill does NOT create role collections.
Role collections are owned by
authentication-xsuaa. Thexs-security.jsongenerated by that skill defines the role collection names and role-template references. This skill only links the deployed role-templates into those collections and assigns users.
The "Everyone" role cannot be directly migrated. In Neo, the Everyone role grants access to all authenticated users implicitly. In CF, you must explicitly assign role collections to all users. This skill flags this as a manual step.
Prerequisites
subaccount-roles-exportmust have run —~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/neo-roles.jsonmust existAll applications must be deployed to CF — each app's
authentication-xsuaaskill must have run,xs-security.jsonmust be deployed, andcf deploy . -fmust have completed successfully for every appBTP CLI installed and logged in:
btp targetIf not logged in, instruct the user to run:
btp login --ssoTarget CF subaccount ID — the GUID of the CF subaccount
Input Resolution
Step 0: Resolve Required Parameters
0a. Resolve subaccount directory:
_NEO_MIGRATION_HOME="${XDG_DATA_HOME:-${APPDATA:-$HOME}}/.neo-migration"
_NEO_CONSENTS_HOME="${XDG_DATA_HOME:-${APPDATA:-$HOME}}/.neo-migration-consents"
export SUBACCOUNT="<Neo subaccount technical name>"
_CF_ORG=$(cf target 2>/dev/null | awk '/^org:/{print $2}')
CF_SUBACCOUNT_GUID=$(btp list accounts/subaccount 2>/dev/null | awk -v org="${_CF_ORG}" '
NR > 2 { guid=$1; subdomain=$3; if (index(org, subdomain) > 0) { print guid; exit } }
')
[ -n "${CF_SUBACCOUNT_GUID}" ] || { echo "ERROR: Could not resolve CF_SUBACCOUNT_GUID — check btp login and cf target." >&2; exit 1; }
_SUBACCOUNT_DIR="${_NEO_MIGRATION_HOME}/${SUBACCOUNT}/${CF_SUBACCOUNT_GUID}"
When invoked by
subaccount-migration-orchestrator,SUBACCOUNTandCF_SUBACCOUNT_GUIDare already known. When invoked standalone,SUBACCOUNTmust be provided andCF_SUBACCOUNT_GUIDis resolved automatically.
0b. Read the export file:
cat "${_SUBACCOUNT_DIR}/neo-roles.json"
If the file does not exist, stop and instruct the user to run subaccount-roles-export first.
0c. Check for existing CF config:
if [ -f .migration/cf-migration-config.json ]; then
cat .migration/cf-migration-config.json
fi
Read cfSubaccountId if present and assign it to the shell variable:
CF_SUBACCOUNT_ID=$(python3 -c "
import json, os
path = '.migration/cf-migration-config.json'
if os.path.exists(path):
with open(path) as f:
print(json.load(f).get('cfSubaccountId', ''))
" 2>/dev/null || echo "")
0d. Read the trust import report to determine the IdP origin key:
if [ -f "${_SUBACCOUNT_DIR}/neo-trust-import-report.json" ]; then
cat "${_SUBACCOUNT_DIR}/neo-trust-import-report.json"
fi
Extract the originKey from the first successfully imported IdP:
IDP_ORIGIN=$(python3 -c "
import json, sys, os
path = os.path.expanduser('~') + '/.neo-migration/${SUBACCOUNT}/${CF_SUBACCOUNT_GUID}/neo-trust-import-report.json'
try:
with open(path) as f:
report = json.load(f)
imported = report.get('imported', [])
already = report.get('alreadyConfigured', [])
all_idps = imported + already
print(all_idps[0]['originKey'] if all_idps else 'sap.default')
except Exception:
print('sap.default')
" 2>/dev/null || echo "sap.default")
If the file does not exist or contains no imported IdPs, fall back to sap.default and note it in the report:
"No trust migration report found — using
sap.defaultas the IdP origin for user assignments. If users authenticate via a custom IdP, re-run this skill after completingsubaccount-trust-migrator."
0e. Ask the user for any values still missing:
- "What is the GUID of the target CF subaccount?"
0f. Save CF config if not already present:
Write { "cfSubaccountId": "<guid>" } to .migration/cf-migration-config.json if it doesn't exist.
Step 1: Validate Session and Input
1a. Verify BTP CLI session:
btp target
If this fails, stop and tell the user:
"No active BTP CLI session found. Please run
btp login --ssoin your terminal, then re-invoke this skill."
1b. Check for data to process:
If both applications and groups arrays are empty in the export, inform the user:
"The Neo subaccount has no application roles or groups configured. Nothing to import."
Then stop — this is a successful no-op.
Step 2: Resolve Deployed XSUAA Apps
Fetch the list of deployed XSUAA applications from the CF subaccount:
XSUAA_APPS=$(btp --format json list security/app --subaccount "${CF_SUBACCOUNT_ID}")
Build a map of appName → appId using jq:
# For each app name from neo-roles.json, find its deployed appId
# XSUAA appIds have the format: appname!tNNNNN
# Match by the prefix before the '!' separator
# NOTE: BTP CLI returns .appid (lowercase) and .xsappname — not .name or .appId
echo "$XSUAA_APPS" | jq -r '.[] | "\(.xsappname) \(.appid)"'
For each application in neo-roles.json:
APP_NAME="<appName from neo-roles.json>"
APP_ID=$(echo "$XSUAA_APPS" | jq -r --arg name "$APP_NAME" \
'.[] | select(.xsappname == $name or (.appid | startswith($name + "!"))) | .appid' | head -1)
Classification:
| Condition | Action |
|---|---|
APP_ID found |
Proceed to Steps 3 and 4 for this app |
APP_ID not found |
Add to manual checklist: "App {appName} not found in CF — ensure it has been deployed and authentication-xsuaa has been run for it. Re-run this skill after deployment." Skip role-template assignment for this app; still attempt user assignments if role collections exist. |
Step 3: Add Role-Templates to Role Collections
For each application with a resolved APP_ID, for each role in application.roles[]:
3a. Determine the role collection name:
First, try to read xs-security.json from the application directory to find the exact collection name defined there:
# If app directories are known (e.g. from cf-migration-config.json or user input):
APP_DIR="<path to app source directory>"
if [ -f "${APP_DIR}/xs-security.json" ]; then
# Find role-collection that references this role-template
COLLECTION_NAME=$(jq -r --arg role "${ROLE_NAME}" \
'.["role-collections"][] | select(.["role-template-references"][] | contains($role)) | .name' \
"${APP_DIR}/xs-security.json" | head -1)
fi
If xs-security.json is not found or the role is not listed there, fall back to the naming convention:
COLLECTION_NAME="${APP_NAME}-${ROLE_NAME}"
3b. Add the role-template to the collection:
btp add security/role "${ROLE_NAME}" \
--to-role-collection "${COLLECTION_NAME}" \
--of-app "${APP_ID}" \
--of-role-template "${ROLE_NAME}" \
--subaccount "${CF_SUBACCOUNT_ID}"
Handle responses:
| Outcome | Action |
|---|---|
| Success (exit 0) | Mark as role_assigned |
| "already exists" / conflict | Mark as already_assigned — treat as success |
| "read-only RoleCollection" | Mark as already_assigned — role-template is already bound by authentication-xsuaa during deploy; no action needed |
| Role collection not found | Add to manual checklist: "Role collection {collectionName} not found — ensure authentication-xsuaa was run for {appName} and the app is deployed. Create the collection manually or re-run authentication-xsuaa." |
| Any other error | Mark as failed, capture error, continue |
Step 4: Assign Users to Role Collections
4a. Assign users from application role assignments:
For each application, for each role, for each user in role.userAssignments[]:
btp assign security/role-collection "${COLLECTION_NAME}" \
--to-user "${USER_ID}" \
--of-idp "${IDP_ORIGIN}" \
--subaccount "${CF_SUBACCOUNT_ID}"
Handle responses:
| Outcome | Action |
|---|---|
| Success (exit 0) | Record as assigned |
| "user not found" error | Add to manual steps: "User {userId} not found in CF — the user must log in at least once to be created. Assign manually in BTP Cockpit > Security > Users." |
| Any other error | Record as failed, capture error, continue |
4b. Create and populate group role collections:
For each group in groups[]:
The group role collection may already exist if subaccount-trust-migrator created it for assertion-based group rules. Check first:
EXISTING=$(btp --format json list security/role-collection --subaccount "${CF_SUBACCOUNT_ID}" | \
jq -r --arg name "${GROUP_NAME}-Group" '.[] | select(.name == $name) | .name')
If it does not exist, create it:
btp create security/role-collection "${GROUP_NAME}-Group" \
--description "Migrated from Neo group: ${GROUP_NAME}" \
--subaccount "${CF_SUBACCOUNT_ID}" 2>/dev/null || true
Assign users from group.userAssignments[]:
btp assign security/role-collection "${GROUP_NAME}-Group" \
--to-user "${USER_ID}" \
--of-idp "${IDP_ORIGIN}" \
--subaccount "${CF_SUBACCOUNT_ID}"
4c. Document group→role links in the manual checklist:
For each entry in group.roleAssignments[], CF role collections cannot contain other role collections. Each group→role link must be handled by adding the role-template from the referenced app to the group's collection:
btp add security/role "${ROLE_NAME}" \
--to-role-collection "${GROUP_NAME}-Group" \
--of-app "${APP_ID}" \
--of-role-template "${ROLE_NAME}" \
--subaccount "${CF_SUBACCOUNT_ID}"
If the referenced app is not deployed yet, add to manual checklist:
"{groupName}-Group: In Neo this group had role {applicationName}/{roleName}. Add the role-template from
{applicationName}(appId:{appId}) to this role collection after the app is deployed."
Step 5: Build Manual Checklist
Collect all items that could not be automated:
| Condition | Manual Step |
|---|---|
| Always | "The implicit 'Everyone' role in Neo has no CF equivalent. Explicitly assign the appropriate role collection(s) to all users who should have basic authenticated access, or configure a default role collection in the trust configuration." |
| Any app not found in CF | "Deploy {appName} to CF (cf deploy . -f from the app directory), then re-run subaccount-roles-import to complete role-template and user assignments for this app." |
| Any role collection not found | "Role collection {collectionName} not found — ensure authentication-xsuaa was run and the app is deployed. Create manually in BTP Cockpit > Security > Role Collections, then add role-template {roleName} from app {appId}." |
| Any user assignment failure | "User {userId} not found — ensure they have logged in at least once. Assign {collectionName} manually in BTP Cockpit > Security > Users." |
| Any group with unresolved role links | "{groupName}-Group: add role-template {roleName} from app {appId} after deployment." |
Step 6: Save Import Report
Save to ${_SUBACCOUNT_DIR}/neo-roles-import-report.json using the Write tool:
{
"targetSubaccount": "<CF subaccount GUID>",
"importTimestamp": "<ISO 8601 timestamp>",
"sourceFile": "${_SUBACCOUNT_DIR}/neo-roles.json",
"idpOrigin": "<origin key used for user assignments, e.g. nss.migrated or sap.default>",
"appsResolved": [
{ "name": "myapp", "appId": "myapp!t1234" }
],
"appsNotFound": ["otherapp"],
"roleTemplateAssignments": {
"succeeded": 3,
"alreadyAssigned": 1,
"failed": 0,
"failedDetails": []
},
"userAssignments": {
"succeeded": 5,
"failed": 0,
"failedDetails": []
},
"groupCollectionsCreated": ["managers-Group"],
"groupCollectionsAlreadyExist": ["viewers-Group"],
"manualSteps": [
"The implicit 'Everyone' role in Neo has no CF equivalent. Assign role collections to all authenticated users explicitly.",
"Deploy otherapp to CF, then re-run subaccount-roles-import."
],
"requiresManualSteps": true
}
Step 7: Display Summary
Roles Import Complete
=====================
Target subaccount: <ID>
XSUAA Apps Resolved: <count> / <total>
[If any unresolved:] ✗ <appName>: not found in CF — deploy first
Role-Template Assignments:
Succeeded: <count>
Already assigned: <count>
Failed: <count>
User Assignments:
Succeeded: <count>
Failed: <count>
Groups processed: <count>
Collections created: <count>
Collections already exist: <count>
Manual Steps Required: yes
1. <step>
2. <step>
...
Report saved to: ${_SUBACCOUNT_DIR}/neo-roles-import-report.json
Configuration Files
| File | Location | Purpose |
|---|---|---|
neo-roles.json |
~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Input — roles export from subaccount-roles-export |
neo-trust-import-report.json |
~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Input — trust import report; provides the IdP origin key for user assignments |
cf-migration-config.json |
.migration/ |
CF target subaccount details |
neo-roles-import-report.json |
~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Output — assignment results and manual checklist |
CF Services
None — uses BTP CLI only.
Verification
List role collections and check role-template references:
btp --format json list security/role-collection --subaccount <CF_SUBACCOUNT_ID> | \
jq '.[] | {name, roleRefs: [.roleReferences[].roleTemplateName]}'
Verify a specific collection has users assigned:
btp --format json get security/role-collection "myapp-Admin" --subaccount <CF_SUBACCOUNT_ID> | \
jq '{name, users: [.userReferences[].value]}'
List deployed XSUAA apps:
btp --format json list security/app --subaccount <CF_SUBACCOUNT_ID> | \
jq '.[] | {xsappname, appid}'
Common Issues
Issue: "btp: command not found"
Cause: BTP CLI is not installed or not on PATH. Solution: Download from SAP BTP Tools and ensure it is on PATH.
Issue: "No active session / not logged in"
Cause: BTP CLI session has expired.
Solution: Run btp login --sso in your terminal.
Issue: App not found in btp list security/app
Cause: The application has not been deployed to CF yet, or authentication-xsuaa was not run for it.
Solution: Run the full app migration (jakarta → sdk-replacement → authentication-xsuaa → mta-descriptor → cf deploy . -f), then re-run this skill.
Issue: "Role collection not found" when adding role-template
Cause: authentication-xsuaa was not run, or the app deploy failed and xs-security.json was never applied to CF.
Solution: Verify cf services shows the XSUAA service instance. Check btp list security/role-collection for the expected collection name. If missing, re-run authentication-xsuaa and redeploy.
Issue: "User not found" on assignment
Cause: The user has never logged into the CF subaccount. Solution: Ask the user to log in via the application URL or BTP cockpit once. Then assign manually in BTP Cockpit > Security > Users.
Issue: Role collections have wrong names
Cause: The naming convention in xs-security.json differs from {appName}-{roleName}.
Solution: Read the app's xs-security.json to find the exact role collection name, then use btp add security/role "<roleName>" --to-role-collection "<exact-name>" --of-app "<appId>" --of-role-template "<roleName>" manually.
Next Steps
After completing this skill:
- Complete manual steps — handle the Everyone role, deploy missing apps and re-run, fix failed user assignments
- Test access — log in via the approuter URL in a fresh browser session and verify that role-based access works as expected
- Post-migration checklist — see CLAUDE.md for remaining manual BTP Cockpit steps (set default IdP, re-enter destination secrets, assign entitlements)