Организации пользователя и данные организации

ESIA-Adapter : 8.4. Организации пользователя и данные организации


Назначение сценария

Определить, сотрудником каких организаций является пользователь, и получить сведения об организации: реквизиты, филиалы, сотрудников и группы доступа. Сценарий применяется при входе от имени юридического лица, проверке полномочий и заполнении реквизитов.

Сценарий состоит из двух частей, которые можно использовать независимо:

  1. Организации пользователя — список организаций, в которых пользователь состоит, и их идентификаторы (oid).
  2. Данные организации — карточка, филиалы, сотрудники и группы организации с известным oid.

Требования к настройке ЕСИА

ЧастьОбласти доступаСогласие
Организации пользователяopenid fullname usr_org; для работы от имени организации — scope_org в запросене требуется
Данные организацииhttp://esia.gosuslugi.ru/org_inf?org_oid=<OID_ОРГАНИЗАЦИИ>, http://esia.gosuslugi.ru/org_emps?org_oid=<OID_ОРГАНИЗАЦИИ>, http://esia.gosuslugi.ru/org_grps?org_oid=<OID_ОРГАНИЗАЦИИ>; состав областей определяется полномочиями системы в ЕСИАне требуется

Система зарегистрирована в ЕСИА, сертификат загружен (подключение).

Вызовы API ЕСИА

Организации пользователя: GET /rs/prns/{oid}/orgs?embed=(elements).

Данные организации: GET /rs/orgs/{orgOid} — карточка организации; /brhs — филиалы; /emps — сотрудники; /grps — группы; дополнительно /ctts, /addrs, /vhls (контакты, адреса, транспорт организации).

Данные, передаваемые приложению

  • Организации пользователя: количество и названия организаций; oid каждой организации.
  • Данные организации: shortName, fullName, type (LEGAL — юрлицо, AGENCY — орган власти, FOREIGN — филиал иностранного юрлица), ogrn, inn, kpp, leg. Списки филиалов, сотрудников и групп — в массиве elements.

Конфигурация провайдера

Фрагменты конфигурации внешнего провайдера; полный пример — в конце страницы. Значения в <...> задаются при настройке: это параметры конкретной установки.

Организации пользователя

Шаги конвейера после проверки токена и is_trusted:

[
  {
    "t": 0,
    "r": "person",
    "m": "GET",
    "u": "{{.extidp_details.baseUrl}}/rs/prns/{{printf \"%.0f\" .id_token_claims.sub}}",
    "h": {
      "Authorization": "Bearer {{.token.access_token}}"
    }
  },
  {
    "t": 0,
    "r": "orgs",
    "m": "GET",
    "u": "{{.extidp_details.baseUrl}}/rs/prns/{{printf \"%.0f\" .id_token_claims.sub}}/orgs",
    "h": {
      "Authorization": "Bearer {{.token.access_token}}"
    },
    "d": {
      "embed": "(elements)"
    }
  }
]

Обработка ответа (JavaScript-шаг):

{
  "orgs_count": "orgs && orgs.elements ? orgs.elements.length : 0;",
  "orgs_names": "r = \"\"; if (orgs && orgs.elements) { for (i = 0; i < orgs.elements.length; i++) { n = orgs.elements[i].shortName || orgs.elements[i].fullName; if (n) { r += (r ? \"; \" : \"\") + n; } } } r;"
}

Данные организации

Идентификатор организации и области доступа задаются в typeDetails. Области org_inf, org_emps и org_grps содержат oid организации, поэтому он должен быть известен до запроса авторизации:

"orgOid": "<OID_ОРГАНИЗАЦИИ>",
"scope": "openid fullname usr_org http://esia.gosuslugi.ru/org_inf?org_oid=<OID_ОРГАНИЗАЦИИ> http://esia.gosuslugi.ru/org_emps?org_oid=<OID_ОРГАНИЗАЦИИ> http://esia.gosuslugi.ru/org_grps?org_oid=<OID_ОРГАНИЗАЦИИ>"

Шаги конвейера выполняются после шага получения данных пользователя:

[
  {
    "t": 0,
    "r": "org",
    "m": "GET",
    "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}",
    "h": {
      "Authorization": "Bearer {{.token.access_token}}"
    }
  },
  {
    "t": 0,
    "r": "brhs",
    "m": "GET",
    "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}/brhs",
    "h": {
      "Authorization": "Bearer {{.token.access_token}}"
    },
    "d": {
      "embed": "(elements)"
    }
  },
  {
    "t": 0,
    "r": "emps",
    "m": "GET",
    "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}/emps",
    "h": {
      "Authorization": "Bearer {{.token.access_token}}"
    },
    "d": {
      "embed": "(elements)"
    }
  },
  {
    "t": 0,
    "r": "grps",
    "m": "GET",
    "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}/grps",
    "h": {
      "Authorization": "Bearer {{.token.access_token}}"
    },
    "d": {
      "embed": "(elements)"
    }
  }
]

Обработка ответа (JavaScript-шаг):

{
  "org_name": "org && (org.shortName || org.fullName) ? (org.shortName || org.fullName) : \"\";",
  "org_inn": "org && org.inn ? String(org.inn) : \"\";",
  "org_type": "org && org.type ? org.type : \"\";",
  "branches_count": "brhs && brhs.elements ? brhs.elements.length : 0;",
  "employees_count": "emps && emps.elements ? emps.elements.length : 0;",
  "groups_count": "grps && grps.elements ? grps.elements.length : 0;"
}

Отображение в атрибуты

Для сценария из двух частей:

{
  "fn": "{{.person.firstName}}",
  "ln": "{{.person.lastName}}",
  "mn": "{{.person.middleName}}",
  "un": "",
  "e": "",
  "p": "",
  "l": "",
  "xid": "",
  "rid": "state",
  "x": {
    "orgs_count": "{{.orgs_count}}",
    "orgs": "{{.orgs_names}}",
    "org_name": "{{.org_name}}",
    "org_inn": "{{.org_inn}}",
    "org_type": "{{.org_type}}",
    "branches_count": "{{.branches_count}}",
    "employees_count": "{{.employees_count}}",
    "groups_count": "{{.groups_count}}"
  }
}

Ограничения и особенности

  • Список организаций пользователя возвращается без ограничения по oid и доступен при области usr_org. Данные организации запрашиваются отдельной областью доступа с oid в её составе, поэтому oid выбирается до входа: он не может быть получен из результата /orgs в том же входе.
  • Идентификатор организации (orgOid) задаётся в конфигурации провайдера. Вход приложений направляется на провайдер с synonym, равным esia, поэтому в одной установке адаптера работает одна конфигурация; для другой организации используется отдельный экземпляр адаптера или другое значение orgOid. Список организаций пользователя позволяет определить, какое значение задавать.
  • Роли и полномочия организации получаются отдельными методами раздела А.4–А.6 методических рекомендаций.
  • Методы управления данными организации (раздел А.7) этим примером не покрываются: он предназначен только для чтения.

Проверка

  1. Провайдер включить (isActive: true) на тестовом стенде.
  2. Пройти вход тестовой учётной записью ЕСИА.
  3. Проверить /health/ready и атрибуты пользователя в приложении.
  4. При ошибке смотреть журнал LOG_LEVEL=debug (диагностика).

Полный пример конфигурации

Файл конфигурации внешнего провайдера (extidp.json) целиком. Значение typeDetails — строка с JSON; значения в <...> задаются при настройке.

[
  {
    "id": "<UUID_ПРОВАЙДЕРА>",
    "redirectMethod": 0,
    "authorizeUri": "{{.extidp_details.baseUrl}}/aas/oauth2/v2/ac",
    "logoutRedirectMethod": 0,
    "logoutUri": "{{.extidp_details.baseUrl}}/aas/oauth2/api/logout",
    "logoutForm": {
      "redirect_url": "https://<адрес-после-выхода>",
      "client_id": "{{ .extidp_details.clientID }}"
    },
    "type": "esia",
    "name": "case-organizations",
    "synonym": "esia",
    "typeDetails": "{\n  \"baseUrl\": \"https://esia-portal1.test.gosuslugi.ru\",\n  \"clientCertHash\": \"<ХЕШ_СЕРТИФИКАТА_ГОСТ_Р_34.11-2012_256>\",\n  \"clientID\": \"<MNEMONIC_ИС_В_ЕСИА>\",\n  \"cryptoProvider\": {\n    \"name\": \"cprocsp_raw\",\n    \"details\": {\n      \"certPath\": \"/opt/esia-adapter/certs/<сертификат_ИС>.cer\",\n      \"containerName\": \"<ИМЯ_КОНТЕЙНЕРА>\",\n      \"containerPIN\": \"<PIN_КОНТЕЙНЕРА>\",\n      \"sha1Thumbprint\": \"<SHA1_ОТПЕЧАТОК>\",\n      \"hashAlg\": \"GOST3411_2012_256\"\n    }\n  },\n  \"scope\": \"openid fullname usr_org http://esia.gosuslugi.ru/org_inf?org_oid=<OID_ОРГАНИЗАЦИИ> http://esia.gosuslugi.ru/org_emps?org_oid=<OID_ОРГАНИЗАЦИИ> http://esia.gosuslugi.ru/org_grps?org_oid=<OID_ОРГАНИЗАЦИИ>\",\n  \"scopeOrg\": \"\",\n  \"orgOid\": \"<OID_ОРГАНИЗАЦИИ>\"\n}",
    "redirectForm": {
      "response_type": "code",
      "client_id": "{{ .extidp_details.clientID }}",
      "redirect_uri": "{{ .extidp_request.CallbackURI }}",
      "scope": "{{ .extidp_details.scope }}",
      "client_secret": "{{ $ts := timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started }}{{ $clientSecretRaw := join .extidp_details.clientID .extidp_details.scope $ts .extidp_request.ID .extidp_request.CallbackURI }}{{ cryptoSign .context $clientSecretRaw .extidp_details.cryptoProvider | base64 }}",
      "client_certificate_hash": "{{ .extidp_details.clientCertHash }}",
      "timestamp": "{{ timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started }}",
      "state": "{{ .extidp_request.ID }}",
      "access_type": "offline",
      "code_challenge_method": "{{ .extidp_details.cryptoProvider.details.hashAlg }}",
      "code_challenge": "{{ cryptoHashSum .context .code_verifier .extidp_details.cryptoProvider }}"
    },
    "userInfoPipeline": [
      {
        "t": 1,
        "d": {
          "state": "{{ guid }}"
        }
      },
      {
        "t": 0,
        "r": "token",
        "m": "POST",
        "u": "{{.extidp_details.baseUrl}}/aas/oauth2/v3/te",
        "d": {
          "client_certificate_hash": "{{.extidp_details.clientCertHash}}",
          "client_id": "{{.extidp_details.clientID}}",
          "client_secret": "{{$ts := timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started}}{{$clientSecretRaw := join .extidp_details.clientID .extidp_details.scope $ts .state .extidp_request.CallbackURI .code}}{{ cryptoSign .context $clientSecretRaw .extidp_details.cryptoProvider | base64 }}",
          "code": "{{.code}}",
          "grant_type": "authorization_code",
          "redirect_uri": "{{.extidp_request.CallbackURI}}",
          "scope": "{{.extidp_details.scope}}",
          "state": "{{.state}}",
          "timestamp": "{{timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started}}",
          "token_type": "Bearer",
          "code_verifier": "{{ .code_verifier }}"
        }
      },
      {
        "t": 1,
        "d": {
          "verify_id_token": "{{ cryptoVerifyJWT .context .token.id_token .extidp_details.cryptoProvider }}"
        }
      },
      {
        "t": 2,
        "d": {
          "id_token_claims": "jwt(token.id_token)"
        }
      },
      {
        "t": 1,
        "d": {
          "is_trusted": "{{ with index .id_token_claims \"urn:esia:sbj\" \"urn:esia:sbj:is_tru\" }}{{ if not . }}{{ fail \"user is not trusted\" }}{{ end }}{{ end }}"
        }
      },
      {
        "t": 0,
        "r": "person",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/rs/prns/{{printf \"%.0f\" .id_token_claims.sub}}",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "orgs",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/rs/prns/{{printf \"%.0f\" .id_token_claims.sub}}/orgs",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        },
        "d": {
          "embed": "(elements)"
        }
      },
      {
        "t": 0,
        "r": "org",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "brhs",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}/brhs",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        },
        "d": {
          "embed": "(elements)"
        }
      },
      {
        "t": 0,
        "r": "emps",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}/emps",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        },
        "d": {
          "embed": "(elements)"
        }
      },
      {
        "t": 0,
        "r": "grps",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/rs/orgs/{{.extidp_details.orgOid}}/grps",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        },
        "d": {
          "embed": "(elements)"
        }
      },
      {
        "t": 2,
        "d": {
          "orgs_count": "orgs && orgs.elements ? orgs.elements.length : 0;",
          "orgs_names": "r = \"\"; if (orgs && orgs.elements) { for (i = 0; i < orgs.elements.length; i++) { n = orgs.elements[i].shortName || orgs.elements[i].fullName; if (n) { r += (r ? \"; \" : \"\") + n; } } } r;",
          "org_name": "org && (org.shortName || org.fullName) ? (org.shortName || org.fullName) : \"\";",
          "org_inn": "org && org.inn ? String(org.inn) : \"\";",
          "org_type": "org && org.type ? org.type : \"\";",
          "branches_count": "brhs && brhs.elements ? brhs.elements.length : 0;",
          "employees_count": "emps && emps.elements ? emps.elements.length : 0;",
          "groups_count": "grps && grps.elements ? grps.elements.length : 0;"
        }
      }
    ],
    "attrMapping": {
      "fn": "{{.person.firstName}}",
      "ln": "{{.person.lastName}}",
      "mn": "{{.person.middleName}}",
      "un": "",
      "e": "",
      "p": "",
      "l": "",
      "xid": "",
      "rid": "state",
      "x": {
        "orgs_count": "{{.orgs_count}}",
        "orgs": "{{.orgs_names}}",
        "org_name": "{{.org_name}}",
        "org_inn": "{{.org_inn}}",
        "org_type": "{{.org_type}}",
        "branches_count": "{{.branches_count}}",
        "employees_count": "{{.employees_count}}",
        "groups_count": "{{.groups_count}}"
      }
    },
    "isActive": false
  }
]

Обсуждение