@ksvedal/docs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/Starthjelp-CDnMsPRF.js +6 -0
- package/dist/Starthjelp-DTDqsiPb.js +6 -0
- package/dist/_baseUniq-FW_jgYGR.js +477 -0
- package/dist/access_policy-BV-cRgLX.js +17 -0
- package/dist/access_policy-pBOJMSEK.js +17 -0
- package/dist/access_token_lifetime-6ypKk6LU.js +31 -0
- package/dist/access_token_lifetime-CVau9UC3.js +31 -0
- package/dist/allowed_grant_types-BrnsQvWi.js +101 -0
- package/dist/allowed_grant_types-ovkxJeQq.js +101 -0
- package/dist/application_type-BAESO1T4.js +36 -0
- package/dist/application_type-BtouPPrt.js +39 -0
- package/dist/arc-zSbgd3Dl.js +83 -0
- package/dist/architecture-U656AL7Q-bG73RCfJ.js +5 -0
- package/dist/architectureDiagram-VXUJARFQ-DbJ1yKpS.js +4668 -0
- package/dist/authentication_method-B28p58-a.js +43 -0
- package/dist/authentication_method-D8EwmEki.js +43 -0
- package/dist/authorization_lifetime-CfhrWQ9Y.js +39 -0
- package/dist/authorization_lifetime-DrFRbscQ.js +39 -0
- package/dist/backchannel_logout_uri-BTceN7xq.js +33 -0
- package/dist/backchannel_logout_uri-CBdktUL7.js +33 -0
- package/dist/blockDiagram-VD42YOAC-CmPMJI6H.js +2265 -0
- package/dist/breadcrumbs.json +1202 -0
- package/dist/c4Diagram-YG6GDRKO-BtTnslb7.js +1581 -0
- package/dist/channel-B9C38GUl.js +5 -0
- package/dist/chunk-4BX2VUAB-BLcImAIn.js +9 -0
- package/dist/chunk-55IACEB6-Cd_NYMQ6.js +8 -0
- package/dist/chunk-B4BG7PRW-C5mt8tWU.js +1376 -0
- package/dist/chunk-DI55MBZ5-BliqH_si.js +1382 -0
- package/dist/chunk-FMBD7UC4-D8OxNP20.js +19 -0
- package/dist/chunk-QN33PNHL-DBPrGAkF.js +20 -0
- package/dist/chunk-QZHKN3VN-Ce2k3n1F.js +15 -0
- package/dist/chunk-TZMSLE5B-BB3w_n2J.js +64 -0
- package/dist/classDiagram-2ON5EDUG-BJg1PJs9.js +16 -0
- package/dist/classDiagram-v2-WZHVMYZB-BJg1PJs9.js +16 -0
- package/dist/client_types-BmacnRwO.js +17 -0
- package/dist/client_types-Cu_f02j7.js +17 -0
- package/dist/clone-DZF-mLM1.js +8 -0
- package/dist/components/Docs.d.ts +5 -0
- package/dist/components/DocsBreadcrumbs.d.ts +10 -0
- package/dist/components/DocsErrorBoundary.d.ts +21 -0
- package/dist/components/DocsPage.d.ts +10 -0
- package/dist/components/DocsSearch.d.ts +12 -0
- package/dist/components/DocsViewer.d.ts +11 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/useDocsStyles.d.ts +1 -0
- package/dist/cose-bilkent-S5V4N54A-xEniAY-c.js +2608 -0
- package/dist/cytoscape.esm-CjI2IsL8.js +18735 -0
- package/dist/dagre-6UL2VRFP-DWEj74f8.js +446 -0
- package/dist/defaultLocale-BgPVtth8.js +171 -0
- package/dist/delegation_source-B8L65LAZ.js +17 -0
- package/dist/delegation_source-DBE0sh58.js +17 -0
- package/dist/diagram-PSM6KHXK-Z3KtWXiy.js +533 -0
- package/dist/diagram-QEK2KX5R-BVA9QmB8.js +221 -0
- package/dist/diagram-S2PKOQOG-BOmc_fL6.js +143 -0
- package/dist/docs/folder.d.ts +12 -0
- package/dist/docs/types.d.ts +12 -0
- package/dist/entraid-CPkATiHs.js +6 -0
- package/dist/entraid-CSwp8dMQ.js +6 -0
- package/dist/erDiagram-Q2GNP2WA-Blro_6F2.js +842 -0
- package/dist/faq-CqjYqwL1.js +8 -0
- package/dist/faq-TqD11_1a.js +8 -0
- package/dist/flowDiagram-NV44I4VS-BN9iLCEi.js +1627 -0
- package/dist/frontchannel_logout_uri-Bt6bvoBs.js +36 -0
- package/dist/frontchannel_logout_uri-DRGyFXRl.js +36 -0
- package/dist/ganttDiagram-JELNMOA3-CXLPJQlh.js +2670 -0
- package/dist/general--WqS-xp8.js +89 -0
- package/dist/general-B37q4SsA.js +63 -0
- package/dist/general-BCOYLf6V.js +152 -0
- package/dist/general-CQFRRoeE.js +63 -0
- package/dist/general-Dk7lWiBC.js +152 -0
- package/dist/general-QQfgnjEE.js +89 -0
- package/dist/gitGraph-F6HP7TQM-ChFlbGFG.js +5 -0
- package/dist/gitGraphDiagram-NY62KEGX-DXSPVlhd.js +712 -0
- package/dist/graph-dALvSPTP.js +381 -0
- package/dist/index-D_FT2Td-.js +25338 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/info-NVLQJR56-BSQ5ueiP.js +5 -0
- package/dist/infoDiagram-WHAUD3N6-D0hbJwWb.js +24 -0
- package/dist/init-DjUOC4st.js +16 -0
- package/dist/integration_guide-BXkM8zJ-.js +7 -0
- package/dist/integration_guide-CLABphnS.js +313 -0
- package/dist/integration_guide-Ci8Nz8oL.js +313 -0
- package/dist/integration_guide-Ct8RYoMV.js +6 -0
- package/dist/integration_guide-DWfjt6Qk.js +6 -0
- package/dist/integration_guide-DrKTpPnR.js +6 -0
- package/dist/journeyDiagram-XKPGCS4Q-DTU9EVLJ.js +834 -0
- package/dist/kanban-definition-3W4ZIXB7-CcKx9EnU.js +721 -0
- package/dist/katex-C6SjTJMZ.js +11690 -0
- package/dist/layout-DUskCdLZ.js +1441 -0
- package/dist/linear-RKbqvfvG.js +259 -0
- package/dist/mermaid-parser.core-C-16ojim.js +15189 -0
- package/dist/min-CsCJm_uR.js +38 -0
- package/dist/mindmap-definition-VGOIOE7T-LpgPu_oq.js +787 -0
- package/dist/on_behalf_of-EcHpNqmZ.js +36 -0
- package/dist/on_behalf_of-qrlvHfcG.js +36 -0
- package/dist/ordinal-DfAQgscy.js +61 -0
- package/dist/overview-Bw11cTNo.js +21 -0
- package/dist/overview-ZreyAEkN.js +21 -0
- package/dist/packet-BFZMPI3H-CwJrUCZn.js +5 -0
- package/dist/pie-7BOR55EZ-WUF72bRP.js +5 -0
- package/dist/pieDiagram-ADFJNKIX-BRjx2vS_.js +161 -0
- package/dist/pkce-BkSKWYmh.js +34 -0
- package/dist/pkce-C3U_jCxQ.js +33 -0
- package/dist/post_logout_redirect_uri-BSzuTRwg.js +33 -0
- package/dist/post_logout_redirect_uri-BnhzB1De.js +33 -0
- package/dist/pseudonymous_login-B3oa6s2f.js +17 -0
- package/dist/pseudonymous_login-x98obOlL.js +17 -0
- package/dist/quadrantDiagram-AYHSOK5B-BOwjGYKH.js +1024 -0
- package/dist/radar-NHE76QYJ-DRN4buPP.js +5 -0
- package/dist/redirect_uri-Cnlv_2rt.js +38 -0
- package/dist/redirect_uri-DgNidm8d.js +38 -0
- package/dist/refresh_token_lifetime-DCzCzIyu.js +34 -0
- package/dist/refresh_token_lifetime-QcGf0aOG.js +34 -0
- package/dist/refresh_token_usage-C2LdxQHa.js +33 -0
- package/dist/refresh_token_usage-DXI98e4O.js +33 -0
- package/dist/requirementDiagram-UZGBJVZJ-KRDecAgT.js +852 -0
- package/dist/sankeyDiagram-TZEHDZUN-MKxbwv35.js +810 -0
- package/dist/search-index.json +450 -0
- package/dist/sequenceDiagram-WL72ISMW-DEo0cUN3.js +2518 -0
- package/dist/sso-BuAlvelZ.js +79 -0
- package/dist/sso-DYMIpoUd.js +78 -0
- package/dist/stateDiagram-FKZM4ZOC-CaTGomRc.js +263 -0
- package/dist/stateDiagram-v2-4FDKWEC3-l4p7_3uG.js +16 -0
- package/dist/timeline-definition-IT6M3QCI-ChX0PfWC.js +799 -0
- package/dist/token_lifetimes-Cp22x6RM.js +17 -0
- package/dist/token_lifetimes-CwzcMEnb.js +17 -0
- package/dist/token_type-B8DCg80j.js +17 -0
- package/dist/token_type-C7Y04-Fc.js +17 -0
- package/dist/treemap-KMMF4GRG-DNEhU-LQ.js +5 -0
- package/dist/user_involvement-4nbn_fQ7.js +17 -0
- package/dist/user_involvement-COT572uK.js +17 -0
- package/dist/visibility-BSqCGXMv.js +17 -0
- package/dist/visibility-CidZ07d9.js +17 -0
- package/dist/xychartDiagram-PRI3JC2R-CBQAJ13t.js +1340 -0
- package/package.json +60 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const e = `# SSO
|
|
2
|
+
|
|
3
|
+
## Om funksjonaliteten
|
|
4
|
+
ID-porten har siden oppstarten tilbudt single-signon (SSO), ved at alle tjenestene i føderasjonen tilhører samme Circle-of-Trust (CoT). Dette er en viktig funksjonalitet for å at innbygger skal ha en friksjonsfri opplevelse ved bruk av offentlige digitale tjenester, ved at man slipper hyppig re-autentisering. Spesielt for samensatte tjenester, for eksempel såkalte lenketjenester, der innbygger "hopper" mellom ulike etater som del av en komplett tjenesteleveranse, er SSO en nøkkelfunksjonalitet.
|
|
5
|
+
|
|
6
|
+
Like viktig som single signon er single logout. Det er vesentlig for sikkerheten til innbygger at hen blir logget ut av alle tjenester når hen klikker logout. **En feilkonfigurert logout-håndtering hos én kunde kan ødelegge for utlogging hos andre kunder, og gjøre innbygger sårbar for angrep.**
|
|
7
|
+
|
|
8
|
+
## Single Signon (SSO)
|
|
9
|
+
SSO-sesjonen er felles for både OIDC- og SAML-baserte tjenester, og er fra nov. 2023 styrt av Nye ID-porten (OIDC). Sesjonslevetid er felles for alle tjenester uavhengig av sikkerhetsnivå, og denne er 30 minutter, men kan forlenges uten brukerinteraksjon inntil maksimalt 120 minutter, ved å sende en ny autentiseringsforespørsel.
|
|
10
|
+
|
|
11
|
+
Alle tjenester er i utgangspunktet med i samme circle-of-trust, men tjenester kan tvinge frem re-autentisering ved å sette attributten *prompt* til \`login\` i [autentiseringsforespørselen](http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) (tilsvarende *forceAuth* i SAML2). Det er i ny løsning også mulig å konfigurere en integrasjon til å bruke [isolert SSO-sesjon]({{site.baseurl}}/docs/idporten/oidc/oidc_func_nosso).
|
|
12
|
+
|
|
13
|
+
Merk at levetiden på SSO-sesjonen ikke har noen sammenheng med levetiden på utstedte access-token og evt. refresh-tokens.
|
|
14
|
+
|
|
15
|
+
## Single Logout (SLO)
|
|
16
|
+
Alle OIDC-integrasjoner mot ID-porten må implementere støtte for følgende to utloggings-scenario:
|
|
17
|
+
|
|
18
|
+
* Utlogging initiert fra egen tjeneste (endsession)
|
|
19
|
+
* Utlogging initiert fra andre tjenester (front-channel logout)
|
|
20
|
+
|
|
21
|
+
Merk at klienter som utfører endsession OGSÅ vil selv motta et frontkanal-utloggingskall dersom klienten har registrert en \`frontchannel_logout_uri\`.
|
|
22
|
+
|
|
23
|
+
Merk at ID-porten nå støtter [back-channel logout]({{site.baseurl}}/docs/idporten/oidc/oidc_func_backchannel_logout).
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
For SAML-baserte tjenester må også begge utloggingsscenarioene støttes, men oppførselen er ulik - der OIDC sender frontkanalskallene fra iframer på en idporten-styrt side, så vil SAML foreta en kjede av redirects fra tjeneste til tjeneste. SAML-varianten er defor mer sårbar.
|
|
27
|
+
|
|
28
|
+
### 1: Utlogging fra egen tjeneste (/logout)
|
|
29
|
+
|
|
30
|
+
Når brukeren vil logge ut fra din tjeneste, må du sende en redirect (fortrinnsvis POSTe den) til ID-portens endsession-endepunkt \`end_session_endpoint\`. Se [detaljert grensesnittsdefinisjon her](oidc_protocol_logout.html).
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Eksempel:
|
|
34
|
+
\`\`\`
|
|
35
|
+
POST https://idporten.no/logout
|
|
36
|
+
|
|
37
|
+
id_token_hint=eyJraWQiOiJpZ2I1Q3lGT...
|
|
38
|
+
post_logout_redirect_uri=<min registrerte post-logout uri >
|
|
39
|
+
state=<tilstand_jeg_kan_bruke_i_redirect_tilbake_til_meg_pluss_csrf_beskyttelse>
|
|
40
|
+
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
Ved mottak av endsession-redirect, vil ID-porten logge brukeren ut av alle andre tjenester i aktiv SSO-sesjon, både OIDC og SAML. Til slutt vil ID-porten redirecte brukeren til *post_logout_redirect_uri* er oppgitt i request dersom denne er angitt og definert for klient, og *id_token_hint* er inkludert. Dersom disse mangler, vil brukeren ende opp i ID-porten.
|
|
44
|
+
|
|
45
|
+
Utlogging fra egen tjeneste er basert på [OIDC Session Management](http://openid.net/specs/openid-connect-session-1_0.html)-spesifikasjonen.
|
|
46
|
+
|
|
47
|
+
#### Samspill mellom sesjoner og tokens ved utlogging
|
|
48
|
+
|
|
49
|
+
ID-porten vil også invalidere alle tokens som tilhører rene innlogginger (dvs. som kun har scopene "openid" og/eller "profile"). Merk at dette betyr at tokens som inneholder ytterligere scopes, fremdeles vil være aktive etter utlogging. Motivasjonen bak denne oppførselen er at en utlogging fra netttjeneste tilhørende virksomhet A, ikke naturlig skal føre til at langt-levende app-tilgang tilhørende virksomhet B skal trekkes tilbake, om disse to tilfeldigvis ble utstedt med utgangspunkt i samme sso-sesjon.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### 2: Håndtere utlogging fra ID-porten (front-channel logout)
|
|
54
|
+
|
|
55
|
+
Dersom brukeren logger ut fra en annen tjeneste, vil ID-porten trigge utlogging fra alle andre tjenester, dvs. både OIDC-tjenester som er konfigurert med støtte for Front Channel Logout, og SAML-tjenester.
|
|
56
|
+
|
|
57
|
+
ID-porten samler opp informasjon om hvilke tjenester en bruker benytter innenfor en sesjon. For OIDC-klienter som støtter Front Channel Logout, sender ID-porten en GET-forespørsel til klientens *frontchannel_logout_uri*. Parameterne *iss* og *sid* inkluderes for klienter som krever *frontchannel_logout_session_required*. *sid* har samme verdi som claim *sid* i id_token. ID-porten lager en dynamisk side der hver innlogget OIDC-klient får sin egen iframe og blir sendt et front-channel logout-kall i parallell.
|
|
58
|
+
|
|
59
|
+
Merk at siden browser-aktørene stadig strammer inn på tilgangen til 3djeparts-cookies, kan man ikke lenger forvente at egen cookie følger med i front_channel_logout-kallet. Bruk av \`sid\`er derfor eneste fremtidsrettede løsning for å finne igjen egen, lokale brukersesjon.
|
|
60
|
+
|
|
61
|
+
Merk også at klienten som starter utlogging med kall på endsession-endepunktet, også vil motta kall på front channel logout.
|
|
62
|
+
|
|
63
|
+
Eksempel på kall fra ID-porten til klient:
|
|
64
|
+
\`\`\`
|
|
65
|
+
GET https://client.example.com/myapp/logout
|
|
66
|
+
?iss=https://idporten.no/
|
|
67
|
+
&sid=D8Fgz-jEXG7JXP_VAORmAm1sKB0LjZyA3wAy-rVyMYc=
|
|
68
|
+
\`\`\`
|
|
69
|
+
\`sid\` er brukerens sesjons-id som klienten mottok som claim i id-tokenet.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Front-channel logout i ID-porten er basert på [OIDC Front Channel Logout](http://openid.net/specs/openid-connect-frontchannel-1_0.html)-spesifikasjonen.
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
Dersom en klient ikke er konfigurert med Front Channel Logout, vil klienten ikke motta utloggingsforespørsel fra ID-porten dersom brukeren logger ut fra en annen tjeneste i circle-of-trust og id-tokenet vil heller ikke inneholde \`sid\`.
|
|
76
|
+
`;
|
|
77
|
+
export {
|
|
78
|
+
e as default
|
|
79
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const e = `# SSO
|
|
2
|
+
|
|
3
|
+
## Om funksjonaliteten på engelsk
|
|
4
|
+
ID-porten har siden oppstarten tilbudt single-signon (SSO), ved at alle tjenestene i føderasjonen tilhører samme Circle-of-Trust (CoT). Dette er en viktig funksjonalitet for å at innbygger skal ha en friksjonsfri opplevelse ved bruk av offentlige digitale tjenester, ved at man slipper hyppig re-autentisering. Spesielt for samensatte tjenester, for eksempel såkalte lenketjenester, der innbygger "hopper" mellom ulike etater som del av en komplett tjenesteleveranse, er SSO en nøkkelfunksjonalitet.
|
|
5
|
+
|
|
6
|
+
Like viktig som single signon er single logout. Det er vesentlig for sikkerheten til innbygger at hen blir logget ut av alle tjenester når hen klikker logout. **En feilkonfigurert logout-håndtering hos én kunde kan ødelegge for utlogging hos andre kunder, og gjøre innbygger sårbar for angrep.**
|
|
7
|
+
|
|
8
|
+
## Single Signon (SSO)
|
|
9
|
+
SSO-sesjonen er felles for både OIDC- og SAML-baserte tjenester, og er fra nov. 2023 styrt av Nye ID-porten (OIDC). Sesjonslevetid er felles for alle tjenester uavhengig av sikkerhetsnivå, og denne er 30 minutter, men kan forlenges uten brukerinteraksjon inntil maksimalt 120 minutter, ved å sende en ny autentiseringsforespørsel.
|
|
10
|
+
|
|
11
|
+
Alle tjenester er i utgangspunktet med i samme circle-of-trust, men tjenester kan tvinge frem re-autentisering ved å sette attributten *prompt* til \`login\` i [autentiseringsforespørselen](http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) (tilsvarende *forceAuth* i SAML2). Det er i ny løsning også mulig å konfigurere en integrasjon til å bruke [isolert SSO-sesjon]({{site.baseurl}}/docs/idporten/oidc/oidc_func_nosso).
|
|
12
|
+
|
|
13
|
+
Merk at levetiden på SSO-sesjonen ikke har noen sammenheng med levetiden på utstedte access-token og evt. refresh-tokens.
|
|
14
|
+
|
|
15
|
+
## Single Logout (SLO)
|
|
16
|
+
Alle OIDC-integrasjoner mot ID-porten må implementere støtte for følgende to utloggings-scenario:
|
|
17
|
+
|
|
18
|
+
* Utlogging initiert fra egen tjeneste (endsession)
|
|
19
|
+
* Utlogging initiert fra andre tjenester (front-channel logout)
|
|
20
|
+
|
|
21
|
+
Merk at klienter som utfører endsession OGSÅ vil selv motta et frontkanal-utloggingskall dersom klienten har registrert en \`frontchannel_logout_uri\`.
|
|
22
|
+
|
|
23
|
+
Merk at ID-porten nå støtter [back-channel logout]({{site.baseurl}}/docs/idporten/oidc/oidc_func_backchannel_logout).
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
For SAML-baserte tjenester må også begge utloggingsscenarioene støttes, men oppførselen er ulik - der OIDC sender frontkanalskallene fra iframer på en idporten-styrt side, så vil SAML foreta en kjede av redirects fra tjeneste til tjeneste. SAML-varianten er defor mer sårbar.
|
|
27
|
+
|
|
28
|
+
### 1: Utlogging fra egen tjeneste (/logout)
|
|
29
|
+
|
|
30
|
+
Når brukeren vil logge ut fra din tjeneste, må du sende en redirect (fortrinnsvis POSTe den) til ID-portens endsession-endepunkt \`end_session_endpoint\`. Se [detaljert grensesnittsdefinisjon her](oidc_protocol_logout.html).
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Eksempel:
|
|
34
|
+
\`\`\`
|
|
35
|
+
POST https://idporten.no/logout
|
|
36
|
+
|
|
37
|
+
id_token_hint=eyJraWQiOiJpZ2I1Q3lGT...
|
|
38
|
+
post_logout_redirect_uri=<min registrerte post-logout uri >
|
|
39
|
+
state=<tilstand_jeg_kan_bruke_i_redirect_tilbake_til_meg_pluss_csrf_beskyttelse>
|
|
40
|
+
|
|
41
|
+
\`\`\`
|
|
42
|
+
ENGLISH ENGLISH
|
|
43
|
+
Ved mottak av endsession-redirect, vil ID-porten logge brukeren ut av alle andre tjenester i aktiv SSO-sesjon, både OIDC og SAML. Til slutt vil ID-porten redirecte brukeren til *post_logout_redirect_uri* er oppgitt i request dersom denne er angitt og definert for klient, og *id_token_hint* er inkludert. Dersom disse mangler, vil brukeren ende opp i ID-porten.
|
|
44
|
+
|
|
45
|
+
Utlogging fra egen tjeneste er basert på [OIDC Session Management](http://openid.net/specs/openid-connect-session-1_0.html)-spesifikasjonen.
|
|
46
|
+
|
|
47
|
+
#### Samspill mellom sesjoner og tokens ved utlogging
|
|
48
|
+
|
|
49
|
+
ID-porten vil også invalidere alle tokens som tilhører rene innlogginger (dvs. som kun har scopene "openid" og/eller "profile"). Merk at dette betyr at tokens som inneholder ytterligere scopes, fremdeles vil være aktive etter utlogging. Motivasjonen bak denne oppførselen er at en utlogging fra netttjeneste tilhørende virksomhet A, ikke naturlig skal føre til at langt-levende app-tilgang tilhørende virksomhet B skal trekkes tilbake, om disse to tilfeldigvis ble utstedt med utgangspunkt i samme sso-sesjon.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### 2: Håndtere utlogging fra ID-porten (front-channel logout)
|
|
54
|
+
|
|
55
|
+
Dersom brukeren logger ut fra en annen tjeneste, vil ID-porten trigge utlogging fra alle andre tjenester, dvs. både OIDC-tjenester som er konfigurert med støtte for Front Channel Logout, og SAML-tjenester.
|
|
56
|
+
|
|
57
|
+
ID-porten samler opp informasjon om hvilke tjenester en bruker benytter innenfor en sesjon. For OIDC-klienter som støtter Front Channel Logout, sender ID-porten en GET-forespørsel til klientens *frontchannel_logout_uri*. Parameterne *iss* og *sid* inkluderes for klienter som krever *frontchannel_logout_session_required*. *sid* har samme verdi som claim *sid* i id_token. ID-porten lager en dynamisk side der hver innlogget OIDC-klient får sin egen iframe og blir sendt et front-channel logout-kall i parallell.
|
|
58
|
+
|
|
59
|
+
Merk at siden browser-aktørene stadig strammer inn på tilgangen til 3djeparts-cookies, kan man ikke lenger forvente at egen cookie følger med i front_channel_logout-kallet. Bruk av \`sid\`er derfor eneste fremtidsrettede løsning for å finne igjen egen, lokale brukersesjon.
|
|
60
|
+
|
|
61
|
+
Merk også at klienten som starter utlogging med kall på endsession-endepunktet, også vil motta kall på front channel logout.
|
|
62
|
+
|
|
63
|
+
Eksempel på kall fra ID-porten til klient:
|
|
64
|
+
\`\`\`
|
|
65
|
+
GET https://client.example.com/myapp/logout
|
|
66
|
+
?iss=https://idporten.no/
|
|
67
|
+
&sid=D8Fgz-jEXG7JXP_VAORmAm1sKB0LjZyA3wAy-rVyMYc=
|
|
68
|
+
\`\`\`
|
|
69
|
+
\`sid\` er brukerens sesjons-id som klienten mottok som claim i id-tokenet.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Front-channel logout i ID-porten er basert på [OIDC Front Channel Logout](http://openid.net/specs/openid-connect-frontchannel-1_0.html)-spesifikasjonen.
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
Dersom en klient ikke er konfigurert med Front Channel Logout, vil klienten ikke motta utloggingsforespørsel fra ID-porten dersom brukeren logger ut fra en annen tjeneste i circle-of-trust og id-tokenet vil heller ikke inneholde \`sid\`. `;
|
|
76
|
+
export {
|
|
77
|
+
e as default
|
|
78
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { s as R, a as W, S as N } from "./chunk-DI55MBZ5-BliqH_si.js";
|
|
2
|
+
import { _ as f, c as t, d as H, l as S, e as P, k as z, a9 as _, aa as U, a6 as C, u as F } from "./index-D_FT2Td-.js";
|
|
3
|
+
import { G as O } from "./graph-dALvSPTP.js";
|
|
4
|
+
import { l as J } from "./layout-DUskCdLZ.js";
|
|
5
|
+
var X = /* @__PURE__ */ f((e) => e.append("circle").attr("class", "start-state").attr("r", t().state.sizeUnit).attr("cx", t().state.padding + t().state.sizeUnit).attr("cy", t().state.padding + t().state.sizeUnit), "drawStartState"), D = /* @__PURE__ */ f((e) => e.append("line").style("stroke", "grey").style("stroke-dasharray", "3").attr("x1", t().state.textHeight).attr("class", "divider").attr("x2", t().state.textHeight * 2).attr("y1", 0).attr("y2", 0), "drawDivider"), Y = /* @__PURE__ */ f((e, i) => {
|
|
6
|
+
const d = e.append("text").attr("x", 2 * t().state.padding).attr("y", t().state.textHeight + 2 * t().state.padding).attr("font-size", t().state.fontSize).attr("class", "state-title").text(i.id), c = d.node().getBBox();
|
|
7
|
+
return e.insert("rect", ":first-child").attr("x", t().state.padding).attr("y", t().state.padding).attr("width", c.width + 2 * t().state.padding).attr("height", c.height + 2 * t().state.padding).attr("rx", t().state.radius), d;
|
|
8
|
+
}, "drawSimpleState"), I = /* @__PURE__ */ f((e, i) => {
|
|
9
|
+
const d = /* @__PURE__ */ f(function(g, m, B) {
|
|
10
|
+
const E = g.append("tspan").attr("x", 2 * t().state.padding).text(m);
|
|
11
|
+
B || E.attr("dy", t().state.textHeight);
|
|
12
|
+
}, "addTspan"), n = e.append("text").attr("x", 2 * t().state.padding).attr("y", t().state.textHeight + 1.3 * t().state.padding).attr("font-size", t().state.fontSize).attr("class", "state-title").text(i.descriptions[0]).node().getBBox(), l = n.height, x = e.append("text").attr("x", t().state.padding).attr(
|
|
13
|
+
"y",
|
|
14
|
+
l + t().state.padding * 0.4 + t().state.dividerMargin + t().state.textHeight
|
|
15
|
+
).attr("class", "state-description");
|
|
16
|
+
let a = !0, s = !0;
|
|
17
|
+
i.descriptions.forEach(function(g) {
|
|
18
|
+
a || (d(x, g, s), s = !1), a = !1;
|
|
19
|
+
});
|
|
20
|
+
const w = e.append("line").attr("x1", t().state.padding).attr("y1", t().state.padding + l + t().state.dividerMargin / 2).attr("y2", t().state.padding + l + t().state.dividerMargin / 2).attr("class", "descr-divider"), p = x.node().getBBox(), o = Math.max(p.width, n.width);
|
|
21
|
+
return w.attr("x2", o + 3 * t().state.padding), e.insert("rect", ":first-child").attr("x", t().state.padding).attr("y", t().state.padding).attr("width", o + 2 * t().state.padding).attr("height", p.height + l + 2 * t().state.padding).attr("rx", t().state.radius), e;
|
|
22
|
+
}, "drawDescrState"), $ = /* @__PURE__ */ f((e, i, d) => {
|
|
23
|
+
const c = t().state.padding, n = 2 * t().state.padding, l = e.node().getBBox(), x = l.width, a = l.x, s = e.append("text").attr("x", 0).attr("y", t().state.titleShift).attr("font-size", t().state.fontSize).attr("class", "state-title").text(i.id), p = s.node().getBBox().width + n;
|
|
24
|
+
let o = Math.max(p, x);
|
|
25
|
+
o === x && (o = o + n);
|
|
26
|
+
let g;
|
|
27
|
+
const m = e.node().getBBox();
|
|
28
|
+
i.doc, g = a - c, p > x && (g = (x - o) / 2 + c), Math.abs(a - m.x) < c && p > x && (g = a - (p - x) / 2);
|
|
29
|
+
const B = 1 - t().state.textHeight;
|
|
30
|
+
return e.insert("rect", ":first-child").attr("x", g).attr("y", B).attr("class", d ? "alt-composit" : "composit").attr("width", o).attr(
|
|
31
|
+
"height",
|
|
32
|
+
m.height + t().state.textHeight + t().state.titleShift + 1
|
|
33
|
+
).attr("rx", "0"), s.attr("x", g + c), p <= x && s.attr("x", a + (o - n) / 2 - p / 2 + c), e.insert("rect", ":first-child").attr("x", g).attr(
|
|
34
|
+
"y",
|
|
35
|
+
t().state.titleShift - t().state.textHeight - t().state.padding
|
|
36
|
+
).attr("width", o).attr("height", t().state.textHeight * 3).attr("rx", t().state.radius), e.insert("rect", ":first-child").attr("x", g).attr(
|
|
37
|
+
"y",
|
|
38
|
+
t().state.titleShift - t().state.textHeight - t().state.padding
|
|
39
|
+
).attr("width", o).attr("height", m.height + 3 + 2 * t().state.textHeight).attr("rx", t().state.radius), e;
|
|
40
|
+
}, "addTitleAndBox"), q = /* @__PURE__ */ f((e) => (e.append("circle").attr("class", "end-state-outer").attr("r", t().state.sizeUnit + t().state.miniPadding).attr(
|
|
41
|
+
"cx",
|
|
42
|
+
t().state.padding + t().state.sizeUnit + t().state.miniPadding
|
|
43
|
+
).attr(
|
|
44
|
+
"cy",
|
|
45
|
+
t().state.padding + t().state.sizeUnit + t().state.miniPadding
|
|
46
|
+
), e.append("circle").attr("class", "end-state-inner").attr("r", t().state.sizeUnit).attr("cx", t().state.padding + t().state.sizeUnit + 2).attr("cy", t().state.padding + t().state.sizeUnit + 2)), "drawEndState"), Z = /* @__PURE__ */ f((e, i) => {
|
|
47
|
+
let d = t().state.forkWidth, c = t().state.forkHeight;
|
|
48
|
+
if (i.parentId) {
|
|
49
|
+
let n = d;
|
|
50
|
+
d = c, c = n;
|
|
51
|
+
}
|
|
52
|
+
return e.append("rect").style("stroke", "black").style("fill", "black").attr("width", d).attr("height", c).attr("x", t().state.padding).attr("y", t().state.padding);
|
|
53
|
+
}, "drawForkJoinState"), j = /* @__PURE__ */ f((e, i, d, c) => {
|
|
54
|
+
let n = 0;
|
|
55
|
+
const l = c.append("text");
|
|
56
|
+
l.style("text-anchor", "start"), l.attr("class", "noteText");
|
|
57
|
+
let x = e.replace(/\r\n/g, "<br/>");
|
|
58
|
+
x = x.replace(/\n/g, "<br/>");
|
|
59
|
+
const a = x.split(z.lineBreakRegex);
|
|
60
|
+
let s = 1.25 * t().state.noteMargin;
|
|
61
|
+
for (const w of a) {
|
|
62
|
+
const p = w.trim();
|
|
63
|
+
if (p.length > 0) {
|
|
64
|
+
const o = l.append("tspan");
|
|
65
|
+
if (o.text(p), s === 0) {
|
|
66
|
+
const g = o.node().getBBox();
|
|
67
|
+
s += g.height;
|
|
68
|
+
}
|
|
69
|
+
n += s, o.attr("x", i + t().state.noteMargin), o.attr("y", d + n + 1.25 * t().state.noteMargin);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { textWidth: l.node().getBBox().width, textHeight: n };
|
|
73
|
+
}, "_drawLongText"), K = /* @__PURE__ */ f((e, i) => {
|
|
74
|
+
i.attr("class", "state-note");
|
|
75
|
+
const d = i.append("rect").attr("x", 0).attr("y", t().state.padding), c = i.append("g"), { textWidth: n, textHeight: l } = j(e, 0, 0, c);
|
|
76
|
+
return d.attr("height", l + 2 * t().state.noteMargin), d.attr("width", n + t().state.noteMargin * 2), d;
|
|
77
|
+
}, "drawNote"), L = /* @__PURE__ */ f(function(e, i) {
|
|
78
|
+
const d = i.id, c = {
|
|
79
|
+
id: d,
|
|
80
|
+
label: i.id,
|
|
81
|
+
width: 0,
|
|
82
|
+
height: 0
|
|
83
|
+
}, n = e.append("g").attr("id", d).attr("class", "stateGroup");
|
|
84
|
+
i.type === "start" && X(n), i.type === "end" && q(n), (i.type === "fork" || i.type === "join") && Z(n, i), i.type === "note" && K(i.note.text, n), i.type === "divider" && D(n), i.type === "default" && i.descriptions.length === 0 && Y(n, i), i.type === "default" && i.descriptions.length > 0 && I(n, i);
|
|
85
|
+
const l = n.node().getBBox();
|
|
86
|
+
return c.width = l.width + 2 * t().state.padding, c.height = l.height + 2 * t().state.padding, c;
|
|
87
|
+
}, "drawState"), A = 0, Q = /* @__PURE__ */ f(function(e, i, d) {
|
|
88
|
+
const c = /* @__PURE__ */ f(function(s) {
|
|
89
|
+
switch (s) {
|
|
90
|
+
case N.relationType.AGGREGATION:
|
|
91
|
+
return "aggregation";
|
|
92
|
+
case N.relationType.EXTENSION:
|
|
93
|
+
return "extension";
|
|
94
|
+
case N.relationType.COMPOSITION:
|
|
95
|
+
return "composition";
|
|
96
|
+
case N.relationType.DEPENDENCY:
|
|
97
|
+
return "dependency";
|
|
98
|
+
}
|
|
99
|
+
}, "getRelationType");
|
|
100
|
+
i.points = i.points.filter((s) => !Number.isNaN(s.y));
|
|
101
|
+
const n = i.points, l = _().x(function(s) {
|
|
102
|
+
return s.x;
|
|
103
|
+
}).y(function(s) {
|
|
104
|
+
return s.y;
|
|
105
|
+
}).curve(U), x = e.append("path").attr("d", l(n)).attr("id", "edge" + A).attr("class", "transition");
|
|
106
|
+
let a = "";
|
|
107
|
+
if (t().state.arrowMarkerAbsolute && (a = C(!0)), x.attr(
|
|
108
|
+
"marker-end",
|
|
109
|
+
"url(" + a + "#" + c(N.relationType.DEPENDENCY) + "End)"
|
|
110
|
+
), d.title !== void 0) {
|
|
111
|
+
const s = e.append("g").attr("class", "stateLabel"), { x: w, y: p } = F.calcLabelPosition(i.points), o = z.getRows(d.title);
|
|
112
|
+
let g = 0;
|
|
113
|
+
const m = [];
|
|
114
|
+
let B = 0, E = 0;
|
|
115
|
+
for (let u = 0; u <= o.length; u++) {
|
|
116
|
+
const h = s.append("text").attr("text-anchor", "middle").text(o[u]).attr("x", w).attr("y", p + g), y = h.node().getBBox();
|
|
117
|
+
B = Math.max(B, y.width), E = Math.min(E, y.x), S.info(y.x, w, p + g), g === 0 && (g = h.node().getBBox().height, S.info("Title height", g, p)), m.push(h);
|
|
118
|
+
}
|
|
119
|
+
let k = g * o.length;
|
|
120
|
+
if (o.length > 1) {
|
|
121
|
+
const u = (o.length - 1) * g * 0.5;
|
|
122
|
+
m.forEach((h, y) => h.attr("y", p + y * g - u)), k = g * o.length;
|
|
123
|
+
}
|
|
124
|
+
const r = s.node().getBBox();
|
|
125
|
+
s.insert("rect", ":first-child").attr("class", "box").attr("x", w - B / 2 - t().state.padding / 2).attr("y", p - k / 2 - t().state.padding / 2 - 3.5).attr("width", B + t().state.padding).attr("height", k + t().state.padding), S.info(r);
|
|
126
|
+
}
|
|
127
|
+
A++;
|
|
128
|
+
}, "drawEdge"), b, T = {}, V = /* @__PURE__ */ f(function() {
|
|
129
|
+
}, "setConf"), tt = /* @__PURE__ */ f(function(e) {
|
|
130
|
+
e.append("defs").append("marker").attr("id", "dependencyEnd").attr("refX", 19).attr("refY", 7).attr("markerWidth", 20).attr("markerHeight", 28).attr("orient", "auto").append("path").attr("d", "M 19,7 L9,13 L14,7 L9,1 Z");
|
|
131
|
+
}, "insertMarkers"), et = /* @__PURE__ */ f(function(e, i, d, c) {
|
|
132
|
+
b = t().state;
|
|
133
|
+
const n = t().securityLevel;
|
|
134
|
+
let l;
|
|
135
|
+
n === "sandbox" && (l = H("#i" + i));
|
|
136
|
+
const x = n === "sandbox" ? H(l.nodes()[0].contentDocument.body) : H("body"), a = n === "sandbox" ? l.nodes()[0].contentDocument : document;
|
|
137
|
+
S.debug("Rendering diagram " + e);
|
|
138
|
+
const s = x.select(`[id='${i}']`);
|
|
139
|
+
tt(s);
|
|
140
|
+
const w = c.db.getRootDoc();
|
|
141
|
+
G(w, s, void 0, !1, x, a, c);
|
|
142
|
+
const p = b.padding, o = s.node().getBBox(), g = o.width + p * 2, m = o.height + p * 2, B = g * 1.75;
|
|
143
|
+
P(s, m, B, b.useMaxWidth), s.attr(
|
|
144
|
+
"viewBox",
|
|
145
|
+
`${o.x - b.padding} ${o.y - b.padding} ` + g + " " + m
|
|
146
|
+
);
|
|
147
|
+
}, "draw"), at = /* @__PURE__ */ f((e) => e ? e.length * b.fontSizeFactor : 1, "getLabelWidth"), G = /* @__PURE__ */ f((e, i, d, c, n, l, x) => {
|
|
148
|
+
const a = new O({
|
|
149
|
+
compound: !0,
|
|
150
|
+
multigraph: !0
|
|
151
|
+
});
|
|
152
|
+
let s, w = !0;
|
|
153
|
+
for (s = 0; s < e.length; s++)
|
|
154
|
+
if (e[s].stmt === "relation") {
|
|
155
|
+
w = !1;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
d ? a.setGraph({
|
|
159
|
+
rankdir: "LR",
|
|
160
|
+
multigraph: !0,
|
|
161
|
+
compound: !0,
|
|
162
|
+
// acyclicer: 'greedy',
|
|
163
|
+
ranker: "tight-tree",
|
|
164
|
+
ranksep: w ? 1 : b.edgeLengthFactor,
|
|
165
|
+
nodeSep: w ? 1 : 50,
|
|
166
|
+
isMultiGraph: !0
|
|
167
|
+
// ranksep: 5,
|
|
168
|
+
// nodesep: 1
|
|
169
|
+
}) : a.setGraph({
|
|
170
|
+
rankdir: "TB",
|
|
171
|
+
multigraph: !0,
|
|
172
|
+
compound: !0,
|
|
173
|
+
// isCompound: true,
|
|
174
|
+
// acyclicer: 'greedy',
|
|
175
|
+
// ranker: 'longest-path'
|
|
176
|
+
ranksep: w ? 1 : b.edgeLengthFactor,
|
|
177
|
+
nodeSep: w ? 1 : 50,
|
|
178
|
+
ranker: "tight-tree",
|
|
179
|
+
// ranker: 'network-simplex'
|
|
180
|
+
isMultiGraph: !0
|
|
181
|
+
}), a.setDefaultEdgeLabel(function() {
|
|
182
|
+
return {};
|
|
183
|
+
});
|
|
184
|
+
const p = x.db.getStates(), o = x.db.getRelations(), g = Object.keys(p);
|
|
185
|
+
for (const r of g) {
|
|
186
|
+
const u = p[r];
|
|
187
|
+
d && (u.parentId = d);
|
|
188
|
+
let h;
|
|
189
|
+
if (u.doc) {
|
|
190
|
+
let y = i.append("g").attr("id", u.id).attr("class", "stateGroup");
|
|
191
|
+
h = G(u.doc, y, u.id, !c, n, l, x);
|
|
192
|
+
{
|
|
193
|
+
y = $(y, u, c);
|
|
194
|
+
let v = y.node().getBBox();
|
|
195
|
+
h.width = v.width, h.height = v.height + b.padding / 2, T[u.id] = { y: b.compositTitleSize };
|
|
196
|
+
}
|
|
197
|
+
} else
|
|
198
|
+
h = L(i, u, a);
|
|
199
|
+
if (u.note) {
|
|
200
|
+
const y = {
|
|
201
|
+
descriptions: [],
|
|
202
|
+
id: u.id + "-note",
|
|
203
|
+
note: u.note,
|
|
204
|
+
type: "note"
|
|
205
|
+
}, v = L(i, y, a);
|
|
206
|
+
u.note.position === "left of" ? (a.setNode(h.id + "-note", v), a.setNode(h.id, h)) : (a.setNode(h.id, h), a.setNode(h.id + "-note", v)), a.setParent(h.id, h.id + "-group"), a.setParent(h.id + "-note", h.id + "-group");
|
|
207
|
+
} else
|
|
208
|
+
a.setNode(h.id, h);
|
|
209
|
+
}
|
|
210
|
+
S.debug("Count=", a.nodeCount(), a);
|
|
211
|
+
let m = 0;
|
|
212
|
+
o.forEach(function(r) {
|
|
213
|
+
m++, S.debug("Setting edge", r), a.setEdge(
|
|
214
|
+
r.id1,
|
|
215
|
+
r.id2,
|
|
216
|
+
{
|
|
217
|
+
relation: r,
|
|
218
|
+
width: at(r.title),
|
|
219
|
+
height: b.labelHeight * z.getRows(r.title).length,
|
|
220
|
+
labelpos: "c"
|
|
221
|
+
},
|
|
222
|
+
"id" + m
|
|
223
|
+
);
|
|
224
|
+
}), J(a), S.debug("Graph after layout", a.nodes());
|
|
225
|
+
const B = i.node();
|
|
226
|
+
a.nodes().forEach(function(r) {
|
|
227
|
+
r !== void 0 && a.node(r) !== void 0 ? (S.warn("Node " + r + ": " + JSON.stringify(a.node(r))), n.select("#" + B.id + " #" + r).attr(
|
|
228
|
+
"transform",
|
|
229
|
+
"translate(" + (a.node(r).x - a.node(r).width / 2) + "," + (a.node(r).y + (T[r] ? T[r].y : 0) - a.node(r).height / 2) + " )"
|
|
230
|
+
), n.select("#" + B.id + " #" + r).attr("data-x-shift", a.node(r).x - a.node(r).width / 2), l.querySelectorAll("#" + B.id + " #" + r + " .divider").forEach((h) => {
|
|
231
|
+
const y = h.parentElement;
|
|
232
|
+
let v = 0, M = 0;
|
|
233
|
+
y && (y.parentElement && (v = y.parentElement.getBBox().width), M = parseInt(y.getAttribute("data-x-shift"), 10), Number.isNaN(M) && (M = 0)), h.setAttribute("x1", 0 - M + 8), h.setAttribute("x2", v - M - 8);
|
|
234
|
+
})) : S.debug("No Node " + r + ": " + JSON.stringify(a.node(r)));
|
|
235
|
+
});
|
|
236
|
+
let E = B.getBBox();
|
|
237
|
+
a.edges().forEach(function(r) {
|
|
238
|
+
r !== void 0 && a.edge(r) !== void 0 && (S.debug("Edge " + r.v + " -> " + r.w + ": " + JSON.stringify(a.edge(r))), Q(i, a.edge(r), a.edge(r).relation));
|
|
239
|
+
}), E = B.getBBox();
|
|
240
|
+
const k = {
|
|
241
|
+
id: d || "root",
|
|
242
|
+
label: d || "root",
|
|
243
|
+
width: 0,
|
|
244
|
+
height: 0
|
|
245
|
+
};
|
|
246
|
+
return k.width = E.width + 2 * b.padding, k.height = E.height + 2 * b.padding, S.debug("Doc rendered", k, a), k;
|
|
247
|
+
}, "renderDoc"), it = {
|
|
248
|
+
setConf: V,
|
|
249
|
+
draw: et
|
|
250
|
+
}, ot = {
|
|
251
|
+
parser: W,
|
|
252
|
+
get db() {
|
|
253
|
+
return new N(1);
|
|
254
|
+
},
|
|
255
|
+
renderer: it,
|
|
256
|
+
styles: R,
|
|
257
|
+
init: /* @__PURE__ */ f((e) => {
|
|
258
|
+
e.state || (e.state = {}), e.state.arrowMarkerAbsolute = e.arrowMarkerAbsolute;
|
|
259
|
+
}, "init")
|
|
260
|
+
};
|
|
261
|
+
export {
|
|
262
|
+
ot as diagram
|
|
263
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { s as a, b as t, a as r, S as s } from "./chunk-DI55MBZ5-BliqH_si.js";
|
|
2
|
+
import { _ as i } from "./index-D_FT2Td-.js";
|
|
3
|
+
var _ = {
|
|
4
|
+
parser: r,
|
|
5
|
+
get db() {
|
|
6
|
+
return new s(2);
|
|
7
|
+
},
|
|
8
|
+
renderer: t,
|
|
9
|
+
styles: a,
|
|
10
|
+
init: /* @__PURE__ */ i((e) => {
|
|
11
|
+
e.state || (e.state = {}), e.state.arrowMarkerAbsolute = e.arrowMarkerAbsolute;
|
|
12
|
+
}, "init")
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
_ as diagram
|
|
16
|
+
};
|