# Subaccount Roles Import

> Assign Neo application roles and group memberships into a CF subaccount after apps are deployed. Reads ~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/neo-roles.json (produced by subaccount-roles-export), resolves live XSUAA appIds from deployed CF apps, adds role-templates from those apps to the role collections created by authentication-xsuaa, and assigns users via BTP CLI. Consent files live under ~/.neo-migration-consents/$SUBACCOUNT/. Generates a manual checklist for unresolved apps, group role links, and the Everyone role. MUST run after all applications are deployed to CF.

- Skill: `sap-samples/subaccount-roles-import` (Agent Skill)
- Install (CLI): `npx skillmds@latest add sap-samples/subaccount-roles-import`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sap-samples/subaccount-roles-import/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: sap-samples (https://skillmd.com/u/sap-samples)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/sap-samples/subaccount-roles-import

---


# 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 `appId` for 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 first `cf deploy`. Role collections are created by `authentication-xsuaa` via `xs-security.json` + deployment; this skill does NOT create role collections.

> **Role collections are owned by `authentication-xsuaa`.** The `xs-security.json` generated 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

1. **`subaccount-roles-export` must have run** — `~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/neo-roles.json` must exist

2. **All applications must be deployed to CF** — each app's `authentication-xsuaa` skill must have run, `xs-security.json` must be deployed, and `cf deploy . -f` must have completed successfully for every app

3. **BTP CLI installed and logged in:**
   ```bash
   btp target
   ```
   If not logged in, instruct the user to run:
   ```bash
   btp login --sso
   ```

4. **Target CF subaccount ID** — the GUID of the CF subaccount

## Input Resolution

### Step 0: Resolve Required Parameters

**0a. Resolve subaccount directory:**

```bash
_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`, `SUBACCOUNT` and `CF_SUBACCOUNT_GUID` are already known. When invoked standalone, `SUBACCOUNT` must be provided and `CF_SUBACCOUNT_GUID` is resolved automatically.

**0b. Read the export file:**

```bash
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:**

```bash
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:

```bash
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:**

```bash
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:

```bash
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.default` as the IdP origin for user assignments. If users authenticate via a custom IdP, re-run this skill after completing `subaccount-trust-migrator`."

**0e. Ask the user** for any values still missing:

1. "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:**

```bash
btp target
```

If this fails, stop and tell the user:
> "No active BTP CLI session found. Please run `btp login --sso` in 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:

```bash
XSUAA_APPS=$(btp --format json list security/app --subaccount "${CF_SUBACCOUNT_ID}")
```

Build a map of `appName → appId` using jq:

```bash
# 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`:

```bash
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:

```bash
# 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:

```bash
COLLECTION_NAME="${APP_NAME}-${ROLE_NAME}"
```

**3b. Add the role-template to the collection:**

```bash
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[]`:

```bash
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:

```bash
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:

```bash
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[]`:

```bash
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:

```bash
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:

```json
{
  "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:

```bash
btp --format json list security/role-collection --subaccount <CF_SUBACCOUNT_ID> | \
  jq '.[] | {name, roleRefs: [.roleReferences[].roleTemplateName]}'
```

Verify a specific collection has users assigned:

```bash
btp --format json get security/role-collection "myapp-Admin" --subaccount <CF_SUBACCOUNT_ID> | \
  jq '{name, users: [.userReferences[].value]}'
```

List deployed XSUAA apps:

```bash
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)

