diff --git a/404.html b/404.html index f8a9d8f3..6c5bc24c 100644 --- a/404.html +++ b/404.html @@ -3,12 +3,12 @@ -Page Not Found | LmcRbacMvc - - - +Page Not Found | LmcRbacMvc + + + -
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

\ No newline at end of file diff --git a/assets/js/07929a01.3fe981c3.js b/assets/js/07929a01.3fe981c3.js deleted file mode 100644 index 1f1d670c..00000000 --- a/assets/js/07929a01.3fe981c3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[598],{8244:e=>{e.exports=JSON.parse('{"permalink":"/lmcrbacmvc/blog/tags/lmcrbacmvc","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/0e384e19.3d824450.js b/assets/js/0e384e19.3d824450.js new file mode 100644 index 00000000..5b59c2f4 --- /dev/null +++ b/assets/js/0e384e19.3d824450.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[976],{1512:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>r,toc:()=>l});var o=i(4848),s=i(8453);const t={sidebar_position:1},a="Introduction",r={id:"intro",title:"Introduction",description:"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\Permissions\\Rbac",source:"@site/docs/intro.md",sourceDirName:".",slug:"/intro",permalink:"/lmc-rbac-mvc/docs/intro",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",next:{title:"Requirements and Installation",permalink:"/lmc-rbac-mvc/docs/installation"}},c={},l=[{value:"Why should I use an authorization module?",id:"why-should-i-use-an-authorization-module",level:2},{value:"What is the Rbac model?",id:"what-is-the-rbac-model",level:2},{value:"How can I integrate LmcRbacMvc into my application?",id:"how-can-i-integrate-lmcrbacmvc-into-my-application",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.h1,{id:"introduction",children:"Introduction"}),"\n",(0,o.jsx)(n.p,{children:"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\Permissions\\Rbac"}),"\n",(0,o.jsxs)(n.p,{children:["LmcRbacMvc is part of the ",(0,o.jsx)(n.a,{href:"https://lm-commons.github.io",children:"LM-Commons"})," series of community developed packages for Laminas."]}),"\n",(0,o.jsx)(n.p,{children:"LM-Commons is a GitHub organization dedicated to the collaborative\nand community-driven long-term maintenance of packages & libraries based on the Laminas MVC and Components."}),"\n",(0,o.jsxs)(n.admonition,{type:"tip",children:[(0,o.jsx)(n.p,{children:(0,o.jsx)(n.strong,{children:"Important Note:"})}),(0,o.jsxs)(n.p,{children:["If you are migrating from ZfcRbac v2, there are breaking changes to take into account. See the ",(0,o.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/installation#upgrade",children:"Upgrade"})," section for details."]})]}),"\n",(0,o.jsx)(n.h2,{id:"why-should-i-use-an-authorization-module",children:"Why should I use an authorization module?"}),"\n",(0,o.jsxs)(n.p,{children:["The authorization part of an application is an essential aspect of securing your application.\nWhile the ",(0,o.jsx)(n.em,{children:"authentication"})," part tells you who is using your website, the ",(0,o.jsx)(n.em,{children:"authorization"})," answers if the given identity has the permission to\nperform specific actions."]}),"\n",(0,o.jsx)(n.h2,{id:"what-is-the-rbac-model",children:"What is the Rbac model?"}),"\n",(0,o.jsxs)(n.p,{children:["Rbac stands for ",(0,o.jsx)(n.strong,{children:"role-based access control"}),". We use a very simple (albeit powerful) implementation of this model\nthrough the use of the ",(0,o.jsx)(n.a,{href:"https://github.com/zf-fr/rbac",children:"zf-fr/rbac"})," library."]}),"\n",(0,o.jsx)(n.p,{children:"The basic idea of Rbac is to use roles and permissions:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Users"})," can have one or many ",(0,o.jsx)(n.strong,{children:"Roles"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Roles"})," request access to ",(0,o.jsx)(n.strong,{children:"Permissions"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Permissions"})," are granted to ",(0,o.jsx)(n.strong,{children:"Roles"})]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"By default, LmcRbacMvc can be used for two kinds of Rbac model:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Flat RBAC model: in this model, roles cannot have children. This is ideal for smaller applications, as it is easier\nto understand, and the database design is simpler (no need for a join table)."}),"\n",(0,o.jsx)(n.li,{children:"Hierarchical RBAC model: in this model, roles can have child roles. When evaluating if a given role has a\npermission, this model also checks recursively if any of its child roles also have the permission."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"how-can-i-integrate-lmcrbacmvc-into-my-application",children:"How can I integrate LmcRbacMvc into my application?"}),"\n",(0,o.jsx)(n.p,{children:"LmcRbacMvc offers multiple ways to protect your application:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Using ",(0,o.jsx)(n.strong,{children:"Guards"}),': these classes act as "firewalls" that block access to routes and/or controllers. Guards are usually\nconfigured using PHP arrays, and are executed early in the MVC dispatch process. Typically this happens right after\nthe route has been matched.']}),"\n",(0,o.jsxs)(n.li,{children:["Using ",(0,o.jsx)(n.strong,{children:"AuthorizationService"}),": a complementary method is to use the ",(0,o.jsx)(n.code,{children:"AuthorizationService"})," class and inject it into your\nservice classes to protect them from unwanted access."]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"While it is advised to use both methods to make your application even more secure, this is completely optional and you\ncan choose either of them independently."}),"\n",(0,o.jsx)(n.p,{children:"To find out about how you can easily make your existing application more secure, please refer to the following section:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/cookbook#a-real-world-application",children:"Cookbook: A real world example"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>r});var o=i(6540);const s={},t=o.createContext(s);function a(e){const n=o.useContext(t);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),o.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0e384e19.a32221d5.js b/assets/js/0e384e19.a32221d5.js deleted file mode 100644 index 851d12c3..00000000 --- a/assets/js/0e384e19.a32221d5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[976],{1512:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>r,toc:()=>l});var o=i(4848),s=i(8453);const t={sidebar_position:1},a="Introduction",r={id:"intro",title:"Introduction",description:"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\Permissions\\Rbac",source:"@site/docs/intro.md",sourceDirName:".",slug:"/intro",permalink:"/lmcrbacmvc/docs/intro",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",next:{title:"Requirements and Installation",permalink:"/lmcrbacmvc/docs/installation"}},c={},l=[{value:"Why should I use an authorization module?",id:"why-should-i-use-an-authorization-module",level:2},{value:"What is the Rbac model?",id:"what-is-the-rbac-model",level:2},{value:"How can I integrate LmcRbacMvc into my application?",id:"how-can-i-integrate-lmcrbacmvc-into-my-application",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.h1,{id:"introduction",children:"Introduction"}),"\n",(0,o.jsx)(n.p,{children:"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\Permissions\\Rbac"}),"\n",(0,o.jsxs)(n.p,{children:["LmcRbacMvc is part of the ",(0,o.jsx)(n.a,{href:"https://lm-commons.github.io",children:"LM-Commons"})," series of community developed packages for Laminas."]}),"\n",(0,o.jsx)(n.p,{children:"LM-Commons is a GitHub organization dedicated to the collaborative\nand community-driven long-term maintenance of packages & libraries based on the Laminas MVC and Components."}),"\n",(0,o.jsxs)(n.admonition,{type:"tip",children:[(0,o.jsx)(n.p,{children:(0,o.jsx)(n.strong,{children:"Important Note:"})}),(0,o.jsxs)(n.p,{children:["If you are migrating from ZfcRbac v2, there are breaking changes to take into account. See the ",(0,o.jsx)(n.a,{href:"/lmcrbacmvc/docs/installation#upgrade",children:"Upgrade"})," section for details."]})]}),"\n",(0,o.jsx)(n.h2,{id:"why-should-i-use-an-authorization-module",children:"Why should I use an authorization module?"}),"\n",(0,o.jsxs)(n.p,{children:["The authorization part of an application is an essential aspect of securing your application.\nWhile the ",(0,o.jsx)(n.em,{children:"authentication"})," part tells you who is using your website, the ",(0,o.jsx)(n.em,{children:"authorization"})," answers if the given identity has the permission to\nperform specific actions."]}),"\n",(0,o.jsx)(n.h2,{id:"what-is-the-rbac-model",children:"What is the Rbac model?"}),"\n",(0,o.jsxs)(n.p,{children:["Rbac stands for ",(0,o.jsx)(n.strong,{children:"role-based access control"}),". We use a very simple (albeit powerful) implementation of this model\nthrough the use of the ",(0,o.jsx)(n.a,{href:"https://github.com/zf-fr/rbac",children:"zf-fr/rbac"})," library."]}),"\n",(0,o.jsx)(n.p,{children:"The basic idea of Rbac is to use roles and permissions:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Users"})," can have one or many ",(0,o.jsx)(n.strong,{children:"Roles"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Roles"})," request access to ",(0,o.jsx)(n.strong,{children:"Permissions"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Permissions"})," are granted to ",(0,o.jsx)(n.strong,{children:"Roles"})]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"By default, LmcRbacMvc can be used for two kinds of Rbac model:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Flat RBAC model: in this model, roles cannot have children. This is ideal for smaller applications, as it is easier\nto understand, and the database design is simpler (no need for a join table)."}),"\n",(0,o.jsx)(n.li,{children:"Hierarchical RBAC model: in this model, roles can have child roles. When evaluating if a given role has a\npermission, this model also checks recursively if any of its child roles also have the permission."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"how-can-i-integrate-lmcrbacmvc-into-my-application",children:"How can I integrate LmcRbacMvc into my application?"}),"\n",(0,o.jsx)(n.p,{children:"LmcRbacMvc offers multiple ways to protect your application:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Using ",(0,o.jsx)(n.strong,{children:"Guards"}),': these classes act as "firewalls" that block access to routes and/or controllers. Guards are usually\nconfigured using PHP arrays, and are executed early in the MVC dispatch process. Typically this happens right after\nthe route has been matched.']}),"\n",(0,o.jsxs)(n.li,{children:["Using ",(0,o.jsx)(n.strong,{children:"AuthorizationService"}),": a complementary method is to use the ",(0,o.jsx)(n.code,{children:"AuthorizationService"})," class and inject it into your\nservice classes to protect them from unwanted access."]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"While it is advised to use both methods to make your application even more secure, this is completely optional and you\ncan choose either of them independently."}),"\n",(0,o.jsx)(n.p,{children:"To find out about how you can easily make your existing application more secure, please refer to the following section:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"/lmcrbacmvc/docs/cookbook#a-real-world-application",children:"Cookbook: A real world example"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>r});var o=i(6540);const s={},t=o.createContext(s);function a(e){const n=o.useContext(t);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),o.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1c81fc78.f1f88d0c.js b/assets/js/1c81fc78.f1f88d0c.js new file mode 100644 index 00000000..d6427577 --- /dev/null +++ b/assets/js/1c81fc78.f1f88d0c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[982],{953:s=>{s.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog/tags/laminas","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/22465cd7.036f0152.js b/assets/js/22465cd7.036f0152.js deleted file mode 100644 index 82ea51c3..00000000 --- a/assets/js/22465cd7.036f0152.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[166],{5823:(e,r,n)=>{n.r(r),n.d(r,{assets:()=>l,contentTitle:()=>s,default:()=>h,frontMatter:()=>t,metadata:()=>c,toc:()=>d});var o=n(4848),i=n(8453);const t={sidebar_position:4},s="Role providers",c={id:"role-providers",title:"Role providers",description:"In this section, you will learn:",source:"@site/docs/role-providers.md",sourceDirName:".",slug:"/role-providers",permalink:"/lmcrbacmvc/docs/role-providers",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/role-providers.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Quick Start",permalink:"/lmcrbacmvc/docs/quick-start"},next:{title:"Guards",permalink:"/lmcrbacmvc/docs/guards"}},l={},d=[{value:"What are role providers?",id:"what-are-role-providers",level:2},{value:"Identity providers?",id:"identity-providers",level:2},{value:"Create your own identity provider",id:"create-your-own-identity-provider",level:3},{value:"Built-in role providers",id:"built-in-role-providers",level:2},{value:"InMemoryRoleProvider",id:"inmemoryroleprovider",level:3},{value:"ObjectRepositoryRoleProvider",id:"objectrepositoryroleprovider",level:3},{value:"Creating custom role providers",id:"creating-custom-role-providers",level:2}];function a(e){const r={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(r.h1,{id:"role-providers",children:"Role providers"}),"\n",(0,o.jsx)(r.p,{children:"In this section, you will learn:"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsx)(r.li,{children:"What are role providers"}),"\n",(0,o.jsx)(r.li,{children:"What are identity providers"}),"\n",(0,o.jsx)(r.li,{children:"How to use and configure built-in providers"}),"\n",(0,o.jsx)(r.li,{children:"How to create custom role providers"}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"what-are-role-providers",children:"What are role providers?"}),"\n",(0,o.jsxs)(r.p,{children:["A role provider is an object that returns a list of roles. Each role provider must implement the\n",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Role\\RoleProviderInterface"})," interface. The only required method is ",(0,o.jsx)(r.code,{children:"getRoles"}),", and must return an array\nof ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\RoleInterface"})," objects."]}),"\n",(0,o.jsx)(r.p,{children:"Roles can come from one of many sources: in memory, from a file, from a database... However, please note that\nyou can specify only one role provider per application. The reason is that having multiple role providers makes\nthe workflow harder and can lead to security problems that are very hard to spot."}),"\n",(0,o.jsx)(r.h2,{id:"identity-providers",children:"Identity providers?"}),"\n",(0,o.jsxs)(r.p,{children:["Identity providers return the current identity. Most of the time, this means the logged in user. LmcRbacMvc comes with a\ndefault identity provider (",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Identity\\AuthenticationIdentityProvider"}),") that uses the\n",(0,o.jsx)(r.code,{children:"Laminas\\Authentication\\AuthenticationService"})," service."]}),"\n",(0,o.jsx)(r.h3,{id:"create-your-own-identity-provider",children:"Create your own identity provider"}),"\n",(0,o.jsxs)(r.p,{children:["If you want to implement your own identity provider, create a new class that implements\n",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Identity\\IdentityProviderInterface"})," class. Then, change the ",(0,o.jsx)(r.code,{children:"identity_provider"})," option in LmcRbacMvc config,\nas shown below:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'identity_provider' => 'MyCustomIdentityProvider'\n ]\n];\n"})}),"\n",(0,o.jsx)(r.p,{children:"The identity provider is automatically pulled from the service manager."}),"\n",(0,o.jsx)(r.h2,{id:"built-in-role-providers",children:"Built-in role providers"}),"\n",(0,o.jsxs)(r.p,{children:["LmcRbacMvc comes with two built-in role providers: ",(0,o.jsx)(r.code,{children:"InMemoryRoleProvider"})," and ",(0,o.jsx)(r.code,{children:"ObjectRepositoryRoleProvider"}),". A role\nprovider must be added to the ",(0,o.jsx)(r.code,{children:"role_provider"})," subkey:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n // Role provider config here!\n ]\n ]\n];\n"})}),"\n",(0,o.jsx)(r.h3,{id:"inmemoryroleprovider",children:"InMemoryRoleProvider"}),"\n",(0,o.jsx)(r.p,{children:"This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple\nPHP file, so you never hit a database."}),"\n",(0,o.jsx)(r.p,{children:"Here is an example of the format you need to use:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n 'admin' => [\n 'children' => ['member'],\n 'permissions' => ['article.delete']\n ],\n 'member' => [\n 'children' => ['guest'],\n 'permissions' => ['article.edit', 'article.archive']\n ],\n 'guest' => [\n 'permissions' => ['article.read']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["The ",(0,o.jsx)(r.code,{children:"children"})," and ",(0,o.jsx)(r.code,{children:"permissions"})," subkeys are entirely optional. Internally, the ",(0,o.jsx)(r.code,{children:"InMemoryRoleProvider"})," creates\neither a ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\Role"})," object if the role does not have any children, or a ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\HierarchicalRole"})," if\nthe role has at least one child."]}),"\n",(0,o.jsx)(r.p,{children:"If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n 'admin' => [\n 'permissions' => [\n 'article.delete',\n 'article.edit',\n 'article.archive',\n 'article.read'\n ]\n ],\n 'member' => [\n 'permissions' => [\n 'article.edit',\n 'article.archive',\n 'article.read'\n ]\n ],\n 'guest' => [\n 'permissions' => ['article.read']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsx)(r.h3,{id:"objectrepositoryroleprovider",children:"ObjectRepositoryRoleProvider"}),"\n",(0,o.jsxs)(r.p,{children:["This provider fetches roles from the database using ",(0,o.jsx)(r.code,{children:"Doctrine\\Common\\Persistence\\ObjectRepository"})," interface."]}),"\n",(0,o.jsxs)(r.p,{children:["You can configure this provider by giving an object repository service name that is fetched from the service manager\nusing the ",(0,o.jsx)(r.code,{children:"object_repository"})," key:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_repository' => 'App\\Repository\\RoleRepository',\n 'role_name_property' => 'name'\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["Or you can specify the ",(0,o.jsx)(r.code,{children:"object_manager"})," and ",(0,o.jsx)(r.code,{children:"class_name"})," options:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_manager' => 'doctrine.entitymanager.orm_default',\n 'class_name' => 'App\\Entity\\Role',\n 'role_name_property' => 'name'\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["In both cases, you need to specify the ",(0,o.jsx)(r.code,{children:"role_name_property"})," value, which is the name of the entity's property\nthat holds the actual role name. This is used internally to only load the identity roles, instead of loading\nthe whole table every time."]}),"\n",(0,o.jsxs)(r.p,{children:["Please note that your entity fetched from the table MUST implement the ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\RoleInterface"})," interface."]}),"\n",(0,o.jsx)(r.h2,{id:"creating-custom-role-providers",children:"Creating custom role providers"}),"\n",(0,o.jsxs)(r.p,{children:["To create a custom role provider, you first need to create a class that implements the ",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Role\\RoleProviderInterface"}),"\ninterface."]}),"\n",(0,o.jsx)(r.p,{children:"Then, you need to add it to the role provider manager:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider_manager' => [\n 'factories' => [\n 'Application\\Role\\CustomRoleProvider' => 'Application\\Factory\\CustomRoleProviderFactory'\n ]\n ] \n ]\n];\n"})}),"\n",(0,o.jsx)(r.p,{children:"You can now use it like any other role provider:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'Application\\Role\\CustomRoleProvider' => [\n // Options\n ]\n ]\n ]\n];\n"})})]})}function h(e={}){const{wrapper:r}={...(0,i.R)(),...e.components};return r?(0,o.jsx)(r,{...e,children:(0,o.jsx)(a,{...e})}):a(e)}},8453:(e,r,n)=>{n.d(r,{R:()=>s,x:()=>c});var o=n(6540);const i={},t=o.createContext(i);function s(e){const r=o.useContext(t);return o.useMemo((function(){return"function"==typeof e?e(r):{...r,...e}}),[r,e])}function c(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:s(e.components),o.createElement(t.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/22465cd7.9e45649f.js b/assets/js/22465cd7.9e45649f.js new file mode 100644 index 00000000..0dc1fc54 --- /dev/null +++ b/assets/js/22465cd7.9e45649f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[166],{5823:(e,r,n)=>{n.r(r),n.d(r,{assets:()=>l,contentTitle:()=>s,default:()=>h,frontMatter:()=>t,metadata:()=>c,toc:()=>d});var o=n(4848),i=n(8453);const t={sidebar_position:4},s="Role providers",c={id:"role-providers",title:"Role providers",description:"In this section, you will learn:",source:"@site/docs/role-providers.md",sourceDirName:".",slug:"/role-providers",permalink:"/lmc-rbac-mvc/docs/role-providers",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/role-providers.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Quick Start",permalink:"/lmc-rbac-mvc/docs/quick-start"},next:{title:"Guards",permalink:"/lmc-rbac-mvc/docs/guards"}},l={},d=[{value:"What are role providers?",id:"what-are-role-providers",level:2},{value:"Identity providers?",id:"identity-providers",level:2},{value:"Create your own identity provider",id:"create-your-own-identity-provider",level:3},{value:"Built-in role providers",id:"built-in-role-providers",level:2},{value:"InMemoryRoleProvider",id:"inmemoryroleprovider",level:3},{value:"ObjectRepositoryRoleProvider",id:"objectrepositoryroleprovider",level:3},{value:"Creating custom role providers",id:"creating-custom-role-providers",level:2}];function a(e){const r={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(r.h1,{id:"role-providers",children:"Role providers"}),"\n",(0,o.jsx)(r.p,{children:"In this section, you will learn:"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsx)(r.li,{children:"What are role providers"}),"\n",(0,o.jsx)(r.li,{children:"What are identity providers"}),"\n",(0,o.jsx)(r.li,{children:"How to use and configure built-in providers"}),"\n",(0,o.jsx)(r.li,{children:"How to create custom role providers"}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"what-are-role-providers",children:"What are role providers?"}),"\n",(0,o.jsxs)(r.p,{children:["A role provider is an object that returns a list of roles. Each role provider must implement the\n",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Role\\RoleProviderInterface"})," interface. The only required method is ",(0,o.jsx)(r.code,{children:"getRoles"}),", and must return an array\nof ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\RoleInterface"})," objects."]}),"\n",(0,o.jsx)(r.p,{children:"Roles can come from one of many sources: in memory, from a file, from a database... However, please note that\nyou can specify only one role provider per application. The reason is that having multiple role providers makes\nthe workflow harder and can lead to security problems that are very hard to spot."}),"\n",(0,o.jsx)(r.h2,{id:"identity-providers",children:"Identity providers?"}),"\n",(0,o.jsxs)(r.p,{children:["Identity providers return the current identity. Most of the time, this means the logged in user. LmcRbacMvc comes with a\ndefault identity provider (",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Identity\\AuthenticationIdentityProvider"}),") that uses the\n",(0,o.jsx)(r.code,{children:"Laminas\\Authentication\\AuthenticationService"})," service."]}),"\n",(0,o.jsx)(r.h3,{id:"create-your-own-identity-provider",children:"Create your own identity provider"}),"\n",(0,o.jsxs)(r.p,{children:["If you want to implement your own identity provider, create a new class that implements\n",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Identity\\IdentityProviderInterface"})," class. Then, change the ",(0,o.jsx)(r.code,{children:"identity_provider"})," option in LmcRbacMvc config,\nas shown below:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'identity_provider' => 'MyCustomIdentityProvider'\n ]\n];\n"})}),"\n",(0,o.jsx)(r.p,{children:"The identity provider is automatically pulled from the service manager."}),"\n",(0,o.jsx)(r.h2,{id:"built-in-role-providers",children:"Built-in role providers"}),"\n",(0,o.jsxs)(r.p,{children:["LmcRbacMvc comes with two built-in role providers: ",(0,o.jsx)(r.code,{children:"InMemoryRoleProvider"})," and ",(0,o.jsx)(r.code,{children:"ObjectRepositoryRoleProvider"}),". A role\nprovider must be added to the ",(0,o.jsx)(r.code,{children:"role_provider"})," subkey:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n // Role provider config here!\n ]\n ]\n];\n"})}),"\n",(0,o.jsx)(r.h3,{id:"inmemoryroleprovider",children:"InMemoryRoleProvider"}),"\n",(0,o.jsx)(r.p,{children:"This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple\nPHP file, so you never hit a database."}),"\n",(0,o.jsx)(r.p,{children:"Here is an example of the format you need to use:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n 'admin' => [\n 'children' => ['member'],\n 'permissions' => ['article.delete']\n ],\n 'member' => [\n 'children' => ['guest'],\n 'permissions' => ['article.edit', 'article.archive']\n ],\n 'guest' => [\n 'permissions' => ['article.read']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["The ",(0,o.jsx)(r.code,{children:"children"})," and ",(0,o.jsx)(r.code,{children:"permissions"})," subkeys are entirely optional. Internally, the ",(0,o.jsx)(r.code,{children:"InMemoryRoleProvider"})," creates\neither a ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\Role"})," object if the role does not have any children, or a ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\HierarchicalRole"})," if\nthe role has at least one child."]}),"\n",(0,o.jsx)(r.p,{children:"If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n 'admin' => [\n 'permissions' => [\n 'article.delete',\n 'article.edit',\n 'article.archive',\n 'article.read'\n ]\n ],\n 'member' => [\n 'permissions' => [\n 'article.edit',\n 'article.archive',\n 'article.read'\n ]\n ],\n 'guest' => [\n 'permissions' => ['article.read']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsx)(r.h3,{id:"objectrepositoryroleprovider",children:"ObjectRepositoryRoleProvider"}),"\n",(0,o.jsxs)(r.p,{children:["This provider fetches roles from the database using ",(0,o.jsx)(r.code,{children:"Doctrine\\Common\\Persistence\\ObjectRepository"})," interface."]}),"\n",(0,o.jsxs)(r.p,{children:["You can configure this provider by giving an object repository service name that is fetched from the service manager\nusing the ",(0,o.jsx)(r.code,{children:"object_repository"})," key:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_repository' => 'App\\Repository\\RoleRepository',\n 'role_name_property' => 'name'\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["Or you can specify the ",(0,o.jsx)(r.code,{children:"object_manager"})," and ",(0,o.jsx)(r.code,{children:"class_name"})," options:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_manager' => 'doctrine.entitymanager.orm_default',\n 'class_name' => 'App\\Entity\\Role',\n 'role_name_property' => 'name'\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["In both cases, you need to specify the ",(0,o.jsx)(r.code,{children:"role_name_property"})," value, which is the name of the entity's property\nthat holds the actual role name. This is used internally to only load the identity roles, instead of loading\nthe whole table every time."]}),"\n",(0,o.jsxs)(r.p,{children:["Please note that your entity fetched from the table MUST implement the ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\RoleInterface"})," interface."]}),"\n",(0,o.jsx)(r.h2,{id:"creating-custom-role-providers",children:"Creating custom role providers"}),"\n",(0,o.jsxs)(r.p,{children:["To create a custom role provider, you first need to create a class that implements the ",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Role\\RoleProviderInterface"}),"\ninterface."]}),"\n",(0,o.jsx)(r.p,{children:"Then, you need to add it to the role provider manager:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider_manager' => [\n 'factories' => [\n 'Application\\Role\\CustomRoleProvider' => 'Application\\Factory\\CustomRoleProviderFactory'\n ]\n ] \n ]\n];\n"})}),"\n",(0,o.jsx)(r.p,{children:"You can now use it like any other role provider:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'Application\\Role\\CustomRoleProvider' => [\n // Options\n ]\n ]\n ]\n];\n"})})]})}function h(e={}){const{wrapper:r}={...(0,i.R)(),...e.components};return r?(0,o.jsx)(r,{...e,children:(0,o.jsx)(a,{...e})}):a(e)}},8453:(e,r,n)=>{n.d(r,{R:()=>s,x:()=>c});var o=n(6540);const i={},t=o.createContext(i);function s(e){const r=o.useContext(t);return o.useMemo((function(){return"function"==typeof e?e(r):{...r,...e}}),[r,e])}function c(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:s(e.components),o.createElement(t.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/266b150c.c169f2f7.js b/assets/js/266b150c.c169f2f7.js new file mode 100644 index 00000000..04b0f354 --- /dev/null +++ b/assets/js/266b150c.c169f2f7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[346],{5614:e=>{e.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/2fbe5bb1.0bd05a4d.js b/assets/js/2fbe5bb1.0bd05a4d.js deleted file mode 100644 index 2b789999..00000000 --- a/assets/js/2fbe5bb1.0bd05a4d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[738],{9421:s=>{s.exports=JSON.parse('{"label":"PHP","permalink":"/lmcrbacmvc/blog/tags/php","allTagsPath":"/lmcrbacmvc/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/378f56c0.435ff6a7.js b/assets/js/378f56c0.435ff6a7.js deleted file mode 100644 index 7a37c81d..00000000 --- a/assets/js/378f56c0.435ff6a7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[639],{6060:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>i,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>m,toc:()=>s});var c=n(4848),o=n(8453);const a={slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},r=void 0,m={permalink:"/lmcrbacmvc/blog/new-documentation",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md",source:"@site/blog/2024-02-22-New-documentation.md",title:"New documentation",description:"This the new documentation site dedicated to the LmcRbacMvc module.",date:"2024-02-22T00:00:00.000Z",formattedDate:"February 22, 2024",tags:[{label:"laminas",permalink:"/lmcrbacmvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmcrbacmvc/blog/tags/php"},{label:"lmcrbacmvc",permalink:"/lmcrbacmvc/blog/tags/lmcrbacmvc"}],readingTime:.11,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},unlisted:!1,nextItem:{title:"Welcome",permalink:"/lmcrbacmvc/blog/welcome"}},i={authorsImageUrls:[void 0]},s=[];function l(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(e.p,{children:"This the new documentation site dedicated to the LmcRbacMvc module."}),"\n",(0,c.jsx)(e.p,{children:"There are no changes to the code, just improvements in the documentation."})]})}function u(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,c.jsx)(e,{...t,children:(0,c.jsx)(l,{...t})}):l(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>m});var c=n(6540);const o={},a=c.createContext(o);function r(t){const e=c.useContext(a);return c.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function m(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),c.createElement(a.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/378f56c0.f10e3e52.js b/assets/js/378f56c0.f10e3e52.js new file mode 100644 index 00000000..209d88e1 --- /dev/null +++ b/assets/js/378f56c0.f10e3e52.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[639],{6060:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>i,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>m,toc:()=>s});var c=n(4848),o=n(8453);const a={slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},r=void 0,m={permalink:"/lmc-rbac-mvc/blog/new-documentation",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md",source:"@site/blog/2024-02-22-New-documentation.md",title:"New documentation",description:"This the new documentation site dedicated to the LmcRbacMvc module.",date:"2024-02-22T00:00:00.000Z",formattedDate:"February 22, 2024",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"},{label:"lmcrbacmvc",permalink:"/lmc-rbac-mvc/blog/tags/lmcrbacmvc"}],readingTime:.11,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},unlisted:!1,nextItem:{title:"Welcome",permalink:"/lmc-rbac-mvc/blog/welcome"}},i={authorsImageUrls:[void 0]},s=[];function l(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(e.p,{children:"This the new documentation site dedicated to the LmcRbacMvc module."}),"\n",(0,c.jsx)(e.p,{children:"There are no changes to the code, just improvements in the documentation."})]})}function u(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,c.jsx)(e,{...t,children:(0,c.jsx)(l,{...t})}):l(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>m});var c=n(6540);const o={},a=c.createContext(o);function r(t){const e=c.useContext(a);return c.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function m(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),c.createElement(a.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/393be207.805be4b2.js b/assets/js/393be207.805be4b2.js deleted file mode 100644 index f9512288..00000000 --- a/assets/js/393be207.805be4b2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[134],{6602:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>l,frontMatter:()=>r,metadata:()=>c,toc:()=>d});var o=t(4848),a=t(8453);const r={title:"Markdown page example"},s="Markdown page example",c={type:"mdx",permalink:"/lmcrbacmvc/markdown-page",source:"@site/src/pages/markdown-page.md",title:"Markdown page example",description:"You don't need React to write simple standalone pages.",frontMatter:{title:"Markdown page example"},unlisted:!1},p={},d=[];function i(e){const n={h1:"h1",p:"p",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.h1,{id:"markdown-page-example",children:"Markdown page example"}),"\n",(0,o.jsx)(n.p,{children:"You don't need React to write simple standalone pages."})]})}function l(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(i,{...e})}):i(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>s,x:()=>c});var o=t(6540);const a={},r=o.createContext(a);function s(e){const n=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),o.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/393be207.8c825263.js b/assets/js/393be207.8c825263.js new file mode 100644 index 00000000..0564ed72 --- /dev/null +++ b/assets/js/393be207.8c825263.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[134],{6602:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>l,frontMatter:()=>r,metadata:()=>c,toc:()=>d});var o=t(4848),a=t(8453);const r={title:"Markdown page example"},s="Markdown page example",c={type:"mdx",permalink:"/lmc-rbac-mvc/markdown-page",source:"@site/src/pages/markdown-page.md",title:"Markdown page example",description:"You don't need React to write simple standalone pages.",frontMatter:{title:"Markdown page example"},unlisted:!1},p={},d=[];function i(e){const n={h1:"h1",p:"p",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.h1,{id:"markdown-page-example",children:"Markdown page example"}),"\n",(0,o.jsx)(n.p,{children:"You don't need React to write simple standalone pages."})]})}function l(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(i,{...e})}):i(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>s,x:()=>c});var o=t(6540);const a={},r=o.createContext(a);function s(e){const n=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),o.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/3b8c55ea.6cb65196.js b/assets/js/3b8c55ea.6cb65196.js deleted file mode 100644 index 0ddc5012..00000000 --- a/assets/js/3b8c55ea.6cb65196.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[803],{3668:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>s,metadata:()=>r,toc:()=>c});var t=i(4848),o=i(8453);const s={sidebar_position:2,sidebar_label:"Requirements and Installation"},l="Requirements and Installation",r={id:"installation",title:"Requirements and Installation",description:"Requirements",source:"@site/docs/installation.md",sourceDirName:".",slug:"/installation",permalink:"/lmcrbacmvc/docs/installation",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/installation.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2,sidebar_label:"Requirements and Installation"},sidebar:"tutorialSidebar",previous:{title:"Introduction",permalink:"/lmcrbacmvc/docs/intro"},next:{title:"Quick Start",permalink:"/lmcrbacmvc/docs/quick-start"}},a={},c=[{value:"Requirements",id:"requirements",level:2},{value:"Optional",id:"optional",level:2},{value:"Installation",id:"installation",level:2},{value:"Upgrade",id:"upgrade",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"requirements-and-installation",children:"Requirements and Installation"}),"\n",(0,t.jsx)(n.h2,{id:"requirements",children:"Requirements"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"PHP 7.4 or higher"}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/zf-fr/rbac",children:"Zf-fr/Rbac component v1"}),": this is actually a prototype for the ZF3 Rbac component."]}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"http://www.github.com/laminas",children:"Laminas Components 2.x | 3.x or higher"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"optional",children:"Optional"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/doctrine/DoctrineModule",children:"DoctrineModule"}),": if you want to use some built-in role and permission providers."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/laminas/Laminas%5CDeveloperTools",children:"Laminas\\DeveloperTools"}),": if you want to have useful stats added to\nthe Laminas Developer toolbar."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc only officially supports installation through Composer. For Composer documentation, please refer to\n",(0,t.jsx)(n.a,{href:"http://getcomposer.org/",children:"getcomposer.org"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"Install the module:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"$ composer require lm-commons/lmc-rbac-mvc:^3.0\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Enable the module by adding ",(0,t.jsx)(n.code,{children:"LmcRbacMvc"})," key to your ",(0,t.jsx)(n.code,{children:"application.config.php"})," or ",(0,t.jsx)(n.code,{children:"modules.config.php"})," file. Customize the module by copy-pasting\nthe ",(0,t.jsx)(n.code,{children:"lmc_rbac.global.php.dist"})," file to your ",(0,t.jsx)(n.code,{children:"config/autoload"})," folder."]}),"\n",(0,t.jsx)(n.h2,{id:"upgrade",children:"Upgrade"}),"\n",(0,t.jsx)(n.p,{children:"LmcRbacMvc introduces breaking changes from zfcrbac v2:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["[BC] The namespace has been changed from ",(0,t.jsx)(n.code,{children:"ZfcRbac"})," to ",(0,t.jsx)(n.code,{children:"LmcRbacMvc"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:["[BC] The key ",(0,t.jsx)(n.code,{children:"zfc_rbac"})," in autoload and module config files has been replaced\nby the ",(0,t.jsx)(n.code,{children:"lmc_rbac"})," key."]}),"\n",(0,t.jsx)(n.li,{children:"Requires PHP 7.4 or later"}),"\n",(0,t.jsx)(n.li,{children:"Requires Laminas MVC components 3.x or later"}),"\n",(0,t.jsx)(n.li,{children:"Uses PSR-4 autoload"}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>r});var t=i(6540);const o={},s=t.createContext(o);function l(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:l(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/3b8c55ea.95bbb53a.js b/assets/js/3b8c55ea.95bbb53a.js new file mode 100644 index 00000000..cbee10f0 --- /dev/null +++ b/assets/js/3b8c55ea.95bbb53a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[803],{3668:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>s,metadata:()=>r,toc:()=>c});var t=i(4848),o=i(8453);const s={sidebar_position:2,sidebar_label:"Requirements and Installation"},l="Requirements and Installation",r={id:"installation",title:"Requirements and Installation",description:"Requirements",source:"@site/docs/installation.md",sourceDirName:".",slug:"/installation",permalink:"/lmc-rbac-mvc/docs/installation",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/installation.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2,sidebar_label:"Requirements and Installation"},sidebar:"tutorialSidebar",previous:{title:"Introduction",permalink:"/lmc-rbac-mvc/docs/intro"},next:{title:"Quick Start",permalink:"/lmc-rbac-mvc/docs/quick-start"}},a={},c=[{value:"Requirements",id:"requirements",level:2},{value:"Optional",id:"optional",level:2},{value:"Installation",id:"installation",level:2},{value:"Upgrade",id:"upgrade",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"requirements-and-installation",children:"Requirements and Installation"}),"\n",(0,t.jsx)(n.h2,{id:"requirements",children:"Requirements"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"PHP 7.4 or higher"}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/zf-fr/rbac",children:"Zf-fr/Rbac component v1"}),": this is actually a prototype for the ZF3 Rbac component."]}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"http://www.github.com/laminas",children:"Laminas Components 2.x | 3.x or higher"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"optional",children:"Optional"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/doctrine/DoctrineModule",children:"DoctrineModule"}),": if you want to use some built-in role and permission providers."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/laminas/Laminas%5CDeveloperTools",children:"Laminas\\DeveloperTools"}),": if you want to have useful stats added to\nthe Laminas Developer toolbar."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc only officially supports installation through Composer. For Composer documentation, please refer to\n",(0,t.jsx)(n.a,{href:"http://getcomposer.org/",children:"getcomposer.org"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"Install the module:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"$ composer require lm-commons/lmc-rbac-mvc:^3.0\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Enable the module by adding ",(0,t.jsx)(n.code,{children:"LmcRbacMvc"})," key to your ",(0,t.jsx)(n.code,{children:"application.config.php"})," or ",(0,t.jsx)(n.code,{children:"modules.config.php"})," file. Customize the module by copy-pasting\nthe ",(0,t.jsx)(n.code,{children:"lmc_rbac.global.php.dist"})," file to your ",(0,t.jsx)(n.code,{children:"config/autoload"})," folder."]}),"\n",(0,t.jsx)(n.h2,{id:"upgrade",children:"Upgrade"}),"\n",(0,t.jsx)(n.p,{children:"LmcRbacMvc introduces breaking changes from zfcrbac v2:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["[BC] The namespace has been changed from ",(0,t.jsx)(n.code,{children:"ZfcRbac"})," to ",(0,t.jsx)(n.code,{children:"LmcRbacMvc"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:["[BC] The key ",(0,t.jsx)(n.code,{children:"zfc_rbac"})," in autoload and module config files has been replaced\nby the ",(0,t.jsx)(n.code,{children:"lmc_rbac"})," key."]}),"\n",(0,t.jsx)(n.li,{children:"Requires PHP 7.4 or later"}),"\n",(0,t.jsx)(n.li,{children:"Requires Laminas MVC components 3.x or later"}),"\n",(0,t.jsx)(n.li,{children:"Uses PSR-4 autoload"}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>r});var t=i(6540);const o={},s=t.createContext(o);function l(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:l(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/4a900e2d.a37ef9b5.js b/assets/js/4a900e2d.a37ef9b5.js new file mode 100644 index 00000000..d2cb99bf --- /dev/null +++ b/assets/js/4a900e2d.a37ef9b5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[82],{2130:a=>{a.exports=JSON.parse('{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas","allTagsPath":"/lmc-rbac-mvc/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/4b8919da.4dc42bc8.js b/assets/js/4b8919da.4dc42bc8.js new file mode 100644 index 00000000..f265508f --- /dev/null +++ b/assets/js/4b8919da.4dc42bc8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[223],{6624:e=>{e.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog/tags/php","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/4e6224b1.3600afef.js b/assets/js/4e6224b1.3600afef.js new file mode 100644 index 00000000..9a72da95 --- /dev/null +++ b/assets/js/4e6224b1.3600afef.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[107],{2618:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>l});var s=i(4848),t=i(8453);const r={sidebar_position:7},o="Using the Authorization Service",a={id:"using-the-authorization-service",title:"Using the Authorization Service",description:"This section will teach you how to use the AuthorizationService to its full extent.",source:"@site/docs/using-the-authorization-service.md",sourceDirName:".",slug:"/using-the-authorization-service",permalink:"/lmc-rbac-mvc/docs/using-the-authorization-service",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/using-the-authorization-service.md",tags:[],version:"current",sidebarPosition:7,frontMatter:{sidebar_position:7},sidebar:"tutorialSidebar",previous:{title:"Strategies",permalink:"/lmc-rbac-mvc/docs/strategies"},next:{title:"Cookbook",permalink:"/lmc-rbac-mvc/docs/cookbook"}},c={},l=[{value:"Injecting the Authorization Service",id:"injecting-the-authorization-service",level:2},{value:"Using initializers",id:"using-initializers",level:3},{value:"Using delegator factory",id:"using-delegator-factory",level:3},{value:"Using Factories",id:"using-factories",level:3},{value:"Permissions and Assertions",id:"permissions-and-assertions",level:2},{value:"Defining assertions",id:"defining-assertions",level:3},{value:"Defining the assertion map",id:"defining-the-assertion-map",level:3},{value:"Checking permissions in a service",id:"checking-permissions-in-a-service",level:3},{value:"Checking permissions in controllers and views",id:"checking-permissions-in-controllers-and-views",level:3},{value:"In a controller :",id:"in-a-controller-",level:4},{value:"In a view :",id:"in-a-view-",level:4},{value:"Defining additional permissions",id:"defining-additional-permissions",level:3}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",strong:"strong",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"using-the-authorization-service",children:"Using the Authorization Service"}),"\n",(0,s.jsx)(n.p,{children:"This section will teach you how to use the AuthorizationService to its full extent."}),"\n",(0,s.jsx)(n.h2,{id:"injecting-the-authorization-service",children:"Injecting the Authorization Service"}),"\n",(0,s.jsx)(n.h3,{id:"using-initializers",children:"Using initializers"}),"\n",(0,s.jsxs)(n.p,{children:["To automatically inject the authorization service into your classes, you can implement the\n",(0,s.jsx)(n.code,{children:"AuthorizationServiceAwareInterface"})," and use the trait, as shown below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace YourModule;\n\nuse LmcRbacMvc\\Service\\AuthorizationServiceAwareInterface;\nuse LmcRbacMvc\\Service\\AuthorizationServiceAwareTrait;\n\nclass MyClass implements AuthorizationServiceAwareInterface\n{\n use AuthorizationServiceAwareTrait;\n\n public function doSomethingThatRequiresAuth()\n {\n if (! $this->getAuthorizationService()->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n return true;\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then, register the initializer in your config (it is not registered by default):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // ...\n\n public function getServiceConfig()\n {\n return [\n 'initializers' => [\n 'LmcRbacMvc\\Initializer\\AuthorizationServiceInitializer'\n ]\n ];\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"While initializers allow rapid prototyping, their use can lead to more fragile code. We'd suggest using factories."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-delegator-factory",children:"Using delegator factory"}),"\n",(0,s.jsxs)(n.p,{children:["LmcRbacMvc is shipped with a ",(0,s.jsx)(n.code,{children:"LmcRbacMvc\\Factory\\AuthorizationServiceDelegatorFactory"})," ",(0,s.jsx)(n.a,{href:"https://docs.laminas.dev/laminas-servicemanager/delegators/",children:"delegator factory"}),"\nto automatically inject the authorization service into your classes."]}),"\n",(0,s.jsxs)(n.p,{children:["As for the initializer, the class must implement the ",(0,s.jsx)(n.code,{children:"AuthorizationServiceAwareInterface"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You just have to add your classes to the right delegator :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // ...\n\n public function getServiceConfig()\n {\n return [\n 'invokables' => [\n 'Application\\Service\\MyClass' => 'Application\\Service\\MyClassService',\n ],\n 'delegators' => [\n 'Application\\Service\\MyClass' => [\n 'LmcRbacMvc\\Factory\\AuthorizationServiceDelegatorFactory',\n // eventually add more delegators here\n ],\n ],\n ];\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"While they need a little more configuration, delegator factories have better performances than initializers."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-factories",children:"Using Factories"}),"\n",(0,s.jsxs)(n.p,{children:["You can inject the AuthorizationService into your factories by using Laminas' ServiceManager. The AuthorizationService\nis known to the ServiceManager as ",(0,s.jsx)(n.code,{children:"'LmcRbacMvc\\Service\\AuthorizationService'"}),". Here is a classic example for injecting\nthe AuthorizationService:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"YourModule/Module.php"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'MyService' => function($sm) {\n $authService = $sm->get('LmcRbacMvc\\Service\\AuthorizationService');\n return new MyService($authService);\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"permissions-and-assertions",children:"Permissions and Assertions"}),"\n",(0,s.jsx)(n.p,{children:"Since you now know how to inject the AuthorizationService, let's use it!"}),"\n",(0,s.jsxs)(n.p,{children:["One of the great things the AuthorizationService brings are ",(0,s.jsx)(n.strong,{children:"assertions"}),". Assertions get executed ",(0,s.jsx)(n.em,{children:"if the identity\nin fact holds the permission you are requesting"}),". A common example is a blog post, which only the author can edit. In\nthis case, you have a ",(0,s.jsx)(n.code,{children:"post.edit"})," permission and run an assertion checking the author afterwards."]}),"\n",(0,s.jsx)(n.h3,{id:"defining-assertions",children:"Defining assertions"}),"\n",(0,s.jsxs)(n.p,{children:["The AssertionPluginManager is a great way for you to use assertions and IOC. You can add new assertions quite easily\nby adding this to your ",(0,s.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'assertion_manager' => [\n 'factories' => [\n 'MyAssertion' => 'MyAssertionFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.h3,{id:"defining-the-assertion-map",children:"Defining the assertion map"}),"\n",(0,s.jsxs)(n.p,{children:["The assertion map can automatically map permissions to assertions. This means that every time you check for a\npermission with an assertion map, you'll include the assertion in your check. You can define the assertion map by\nadding this to your ",(0,s.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'assertion_map' => [\n 'myPermission' => 'myAssertion'\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Now, every time you check for ",(0,s.jsx)(n.code,{children:"myPermission"}),", ",(0,s.jsx)(n.code,{children:"myAssertion"})," will be checked as well."]}),"\n",(0,s.jsx)(n.h3,{id:"checking-permissions-in-a-service",children:"Checking permissions in a service"}),"\n",(0,s.jsx)(n.p,{children:"So let's check for a permission, shall we?"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$authorizationService->isGranted('myPermission');\n"})}),"\n",(0,s.jsx)(n.p,{children:"That was easy, wasn't it?"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"isGranted"})," checks if the current identity is granted the permission and additionally runs the assertion that is\nprovided by the assertion map."]}),"\n",(0,s.jsx)(n.h3,{id:"checking-permissions-in-controllers-and-views",children:"Checking permissions in controllers and views"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc comes with both a controller plugin and a view helper to check permissions."}),"\n",(0,s.jsx)(n.h4,{id:"in-a-controller-",children:"In a controller :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:" public function doSomethingAction()\n {\n if (!$this->isGranted('myPermission')) {\n // redirect if not granted for example\n }\n }\n"})}),"\n",(0,s.jsx)(n.h4,{id:"in-a-view-",children:"In a view :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:" isGranted('myPermission')): ?>\n
\n

Display only if granted

\n
\n \n"})}),"\n",(0,s.jsx)(n.h3,{id:"defining-additional-permissions",children:"Defining additional permissions"}),"\n",(0,s.jsx)(n.p,{children:"But what if you don't want to use the assertion map? That's quite easy as well!"}),"\n",(0,s.jsx)(n.p,{children:"Here are four examples of how to run an assertion without using the assertion map:"}),"\n",(0,s.jsx)(n.p,{children:"Disable the assertion:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$authorizationService->setAssertion('myPermission', null);\n$authorizationService->isGranted('myPermission');\n"})}),"\n",(0,s.jsx)(n.p,{children:"Callback assertion:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$something = true;\n\n$authorizationService->setAssertion(\n 'myPermission',\n function(AuthorizationService $authorization, $context = true) use ($something) {\n return $something === $context\n }\n);\n\n$authorizationService->isGranted('myPermission'); // returns true, when the identity holds the permission `myPermission`\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Object implementing ",(0,s.jsx)(n.code,{children:"AssertionInterface"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$context = true;\n\n$authorizationService->setAssertion('myPermission', new MyAssertion($foo, $bar));\n$authorizationService->isGranted('myPermission', $context);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Using the AssertionPluginManager:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$context = true;\n$authorizationService->setAssertion('myPermission', 'MyAssertion');\n$authorizationService->isGranted('myPermission', $context);\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Please note: The context parameter is optional!"})})]})}function d(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>o,x:()=>a});var s=i(6540);const t={},r=s.createContext(t);function o(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:o(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/4e6224b1.d8e7dea5.js b/assets/js/4e6224b1.d8e7dea5.js deleted file mode 100644 index 5f5df5c6..00000000 --- a/assets/js/4e6224b1.d8e7dea5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[107],{2618:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>l});var s=i(4848),t=i(8453);const r={sidebar_position:7},o="Using the Authorization Service",a={id:"using-the-authorization-service",title:"Using the Authorization Service",description:"This section will teach you how to use the AuthorizationService to its full extent.",source:"@site/docs/using-the-authorization-service.md",sourceDirName:".",slug:"/using-the-authorization-service",permalink:"/lmcrbacmvc/docs/using-the-authorization-service",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/using-the-authorization-service.md",tags:[],version:"current",sidebarPosition:7,frontMatter:{sidebar_position:7},sidebar:"tutorialSidebar",previous:{title:"Strategies",permalink:"/lmcrbacmvc/docs/strategies"},next:{title:"Cookbook",permalink:"/lmcrbacmvc/docs/cookbook"}},c={},l=[{value:"Injecting the Authorization Service",id:"injecting-the-authorization-service",level:2},{value:"Using initializers",id:"using-initializers",level:3},{value:"Using delegator factory",id:"using-delegator-factory",level:3},{value:"Using Factories",id:"using-factories",level:3},{value:"Permissions and Assertions",id:"permissions-and-assertions",level:2},{value:"Defining assertions",id:"defining-assertions",level:3},{value:"Defining the assertion map",id:"defining-the-assertion-map",level:3},{value:"Checking permissions in a service",id:"checking-permissions-in-a-service",level:3},{value:"Checking permissions in controllers and views",id:"checking-permissions-in-controllers-and-views",level:3},{value:"In a controller :",id:"in-a-controller-",level:4},{value:"In a view :",id:"in-a-view-",level:4},{value:"Defining additional permissions",id:"defining-additional-permissions",level:3}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",strong:"strong",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"using-the-authorization-service",children:"Using the Authorization Service"}),"\n",(0,s.jsx)(n.p,{children:"This section will teach you how to use the AuthorizationService to its full extent."}),"\n",(0,s.jsx)(n.h2,{id:"injecting-the-authorization-service",children:"Injecting the Authorization Service"}),"\n",(0,s.jsx)(n.h3,{id:"using-initializers",children:"Using initializers"}),"\n",(0,s.jsxs)(n.p,{children:["To automatically inject the authorization service into your classes, you can implement the\n",(0,s.jsx)(n.code,{children:"AuthorizationServiceAwareInterface"})," and use the trait, as shown below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace YourModule;\n\nuse LmcRbacMvc\\Service\\AuthorizationServiceAwareInterface;\nuse LmcRbacMvc\\Service\\AuthorizationServiceAwareTrait;\n\nclass MyClass implements AuthorizationServiceAwareInterface\n{\n use AuthorizationServiceAwareTrait;\n\n public function doSomethingThatRequiresAuth()\n {\n if (! $this->getAuthorizationService()->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n return true;\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then, register the initializer in your config (it is not registered by default):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // ...\n\n public function getServiceConfig()\n {\n return [\n 'initializers' => [\n 'LmcRbacMvc\\Initializer\\AuthorizationServiceInitializer'\n ]\n ];\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"While initializers allow rapid prototyping, their use can lead to more fragile code. We'd suggest using factories."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-delegator-factory",children:"Using delegator factory"}),"\n",(0,s.jsxs)(n.p,{children:["LmcRbacMvc is shipped with a ",(0,s.jsx)(n.code,{children:"LmcRbacMvc\\Factory\\AuthorizationServiceDelegatorFactory"})," ",(0,s.jsx)(n.a,{href:"https://docs.laminas.dev/laminas-servicemanager/delegators/",children:"delegator factory"}),"\nto automatically inject the authorization service into your classes."]}),"\n",(0,s.jsxs)(n.p,{children:["As for the initializer, the class must implement the ",(0,s.jsx)(n.code,{children:"AuthorizationServiceAwareInterface"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You just have to add your classes to the right delegator :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // ...\n\n public function getServiceConfig()\n {\n return [\n 'invokables' => [\n 'Application\\Service\\MyClass' => 'Application\\Service\\MyClassService',\n ],\n 'delegators' => [\n 'Application\\Service\\MyClass' => [\n 'LmcRbacMvc\\Factory\\AuthorizationServiceDelegatorFactory',\n // eventually add more delegators here\n ],\n ],\n ];\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"While they need a little more configuration, delegator factories have better performances than initializers."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-factories",children:"Using Factories"}),"\n",(0,s.jsxs)(n.p,{children:["You can inject the AuthorizationService into your factories by using Laminas' ServiceManager. The AuthorizationService\nis known to the ServiceManager as ",(0,s.jsx)(n.code,{children:"'LmcRbacMvc\\Service\\AuthorizationService'"}),". Here is a classic example for injecting\nthe AuthorizationService:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"YourModule/Module.php"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'MyService' => function($sm) {\n $authService = $sm->get('LmcRbacMvc\\Service\\AuthorizationService');\n return new MyService($authService);\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"permissions-and-assertions",children:"Permissions and Assertions"}),"\n",(0,s.jsx)(n.p,{children:"Since you now know how to inject the AuthorizationService, let's use it!"}),"\n",(0,s.jsxs)(n.p,{children:["One of the great things the AuthorizationService brings are ",(0,s.jsx)(n.strong,{children:"assertions"}),". Assertions get executed ",(0,s.jsx)(n.em,{children:"if the identity\nin fact holds the permission you are requesting"}),". A common example is a blog post, which only the author can edit. In\nthis case, you have a ",(0,s.jsx)(n.code,{children:"post.edit"})," permission and run an assertion checking the author afterwards."]}),"\n",(0,s.jsx)(n.h3,{id:"defining-assertions",children:"Defining assertions"}),"\n",(0,s.jsxs)(n.p,{children:["The AssertionPluginManager is a great way for you to use assertions and IOC. You can add new assertions quite easily\nby adding this to your ",(0,s.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'assertion_manager' => [\n 'factories' => [\n 'MyAssertion' => 'MyAssertionFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.h3,{id:"defining-the-assertion-map",children:"Defining the assertion map"}),"\n",(0,s.jsxs)(n.p,{children:["The assertion map can automatically map permissions to assertions. This means that every time you check for a\npermission with an assertion map, you'll include the assertion in your check. You can define the assertion map by\nadding this to your ",(0,s.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'assertion_map' => [\n 'myPermission' => 'myAssertion'\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Now, every time you check for ",(0,s.jsx)(n.code,{children:"myPermission"}),", ",(0,s.jsx)(n.code,{children:"myAssertion"})," will be checked as well."]}),"\n",(0,s.jsx)(n.h3,{id:"checking-permissions-in-a-service",children:"Checking permissions in a service"}),"\n",(0,s.jsx)(n.p,{children:"So let's check for a permission, shall we?"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$authorizationService->isGranted('myPermission');\n"})}),"\n",(0,s.jsx)(n.p,{children:"That was easy, wasn't it?"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"isGranted"})," checks if the current identity is granted the permission and additionally runs the assertion that is\nprovided by the assertion map."]}),"\n",(0,s.jsx)(n.h3,{id:"checking-permissions-in-controllers-and-views",children:"Checking permissions in controllers and views"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc comes with both a controller plugin and a view helper to check permissions."}),"\n",(0,s.jsx)(n.h4,{id:"in-a-controller-",children:"In a controller :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:" public function doSomethingAction()\n {\n if (!$this->isGranted('myPermission')) {\n // redirect if not granted for example\n }\n }\n"})}),"\n",(0,s.jsx)(n.h4,{id:"in-a-view-",children:"In a view :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:" isGranted('myPermission')): ?>\n
\n

Display only if granted

\n
\n \n"})}),"\n",(0,s.jsx)(n.h3,{id:"defining-additional-permissions",children:"Defining additional permissions"}),"\n",(0,s.jsx)(n.p,{children:"But what if you don't want to use the assertion map? That's quite easy as well!"}),"\n",(0,s.jsx)(n.p,{children:"Here are four examples of how to run an assertion without using the assertion map:"}),"\n",(0,s.jsx)(n.p,{children:"Disable the assertion:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$authorizationService->setAssertion('myPermission', null);\n$authorizationService->isGranted('myPermission');\n"})}),"\n",(0,s.jsx)(n.p,{children:"Callback assertion:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$something = true;\n\n$authorizationService->setAssertion(\n 'myPermission',\n function(AuthorizationService $authorization, $context = true) use ($something) {\n return $something === $context\n }\n);\n\n$authorizationService->isGranted('myPermission'); // returns true, when the identity holds the permission `myPermission`\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Object implementing ",(0,s.jsx)(n.code,{children:"AssertionInterface"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$context = true;\n\n$authorizationService->setAssertion('myPermission', new MyAssertion($foo, $bar));\n$authorizationService->isGranted('myPermission', $context);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Using the AssertionPluginManager:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$context = true;\n$authorizationService->setAssertion('myPermission', 'MyAssertion');\n$authorizationService->isGranted('myPermission', $context);\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Please note: The context parameter is optional!"})})]})}function d(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>o,x:()=>a});var s=i(6540);const t={},r=s.createContext(t);function o(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:o(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5b3ddbaa.579400ac.js b/assets/js/5b3ddbaa.579400ac.js new file mode 100644 index 00000000..74705b55 --- /dev/null +++ b/assets/js/5b3ddbaa.579400ac.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[75],{5126:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"new-documentation","metadata":{"permalink":"/lmc-rbac-mvc/blog/new-documentation","editUrl":"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md","source":"@site/blog/2024-02-22-New-documentation.md","title":"New documentation","description":"This the new documentation site dedicated to the LmcRbacMvc module.","date":"2024-02-22T00:00:00.000Z","formattedDate":"February 22, 2024","tags":[{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas"},{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php"},{"label":"lmcrbacmvc","permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc"}],"readingTime":0.11,"hasTruncateMarker":false,"authors":[{"name":"Eric Richer","title":"LM-Commons Administrator","url":"https://github.com/visto9259","imageURL":"https://github.com/visto9259.png","key":"ericr"}],"frontMatter":{"slug":"new-documentation","title":"New documentation","authors":["ericr"],"tags":["laminas","PHP","lmcrbacmvc"]},"unlisted":false,"nextItem":{"title":"Welcome","permalink":"/lmc-rbac-mvc/blog/welcome"}},"content":"This the new documentation site dedicated to the LmcRbacMvc module.\\n\\nThere are no changes to the code, just improvements in the documentation."},{"id":"welcome","metadata":{"permalink":"/lmc-rbac-mvc/blog/welcome","editUrl":"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md","source":"@site/blog/2022-08-02-welcome.md","title":"Welcome","description":"Welcome to the new documentation website for the LM-Commons organization.","date":"2022-08-02T00:00:00.000Z","formattedDate":"August 2, 2022","tags":[{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas"},{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php"}],"readingTime":0.155,"hasTruncateMarker":false,"authors":[{"name":"Eric Richer","title":"LM-Commons Administrator","url":"https://github.com/visto9259","imageURL":"https://github.com/visto9259.png","key":"ericr"}],"frontMatter":{"slug":"welcome","title":"Welcome","authors":["ericr"],"tags":["laminas","PHP"]},"unlisted":false,"prevItem":{"title":"New documentation","permalink":"/lmc-rbac-mvc/blog/new-documentation"}},"content":"Welcome to the new documentation website for the LM-Commons organization.\\n\\nThis site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."}]}')}}]); \ No newline at end of file diff --git a/assets/js/5ccac0b2.dc86f006.js b/assets/js/5ccac0b2.dc86f006.js deleted file mode 100644 index 0c5ef599..00000000 --- a/assets/js/5ccac0b2.dc86f006.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[734],{7123:a=>{a.exports=JSON.parse('{"label":"laminas","permalink":"/lmcrbacmvc/blog/tags/laminas","allTagsPath":"/lmcrbacmvc/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/72e14192.a1fe92a6.js b/assets/js/72e14192.a1fe92a6.js new file mode 100644 index 00000000..06bb7d68 --- /dev/null +++ b/assets/js/72e14192.a1fe92a6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[814],{3744:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>c,toc:()=>d});var t=i(4848),r=i(8453);const o={sidebar_position:3},s="Quick Start",c={id:"quick-start",title:"Quick Start",description:"In this section, you will learn:",source:"@site/docs/quick-start.md",sourceDirName:".",slug:"/quick-start",permalink:"/lmc-rbac-mvc/docs/quick-start",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/quick-start.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Requirements and Installation",permalink:"/lmc-rbac-mvc/docs/installation"},next:{title:"Role providers",permalink:"/lmc-rbac-mvc/docs/role-providers"}},a={},d=[{value:"Specifying an identity provider",id:"specifying-an-identity-provider",level:2},{value:"Adding a guard",id:"adding-a-guard",level:2},{value:"Adding a role provider",id:"adding-a-role-provider",level:2},{value:"Registering a strategy",id:"registering-a-strategy",level:2},{value:"Using the authorization service",id:"using-the-authorization-service",level:2}];function l(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"quick-start",children:"Quick Start"}),"\n",(0,t.jsx)(n.p,{children:"In this section, you will learn:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"How to set up the module"}),"\n",(0,t.jsx)(n.li,{children:"How to specify an identity provider"}),"\n",(0,t.jsx)(n.li,{children:"How to add a simple role provider"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Before starting the quick start, make sure you have properly installed the module by following the instructions in\nthe README file."}),"\n",(0,t.jsx)(n.h2,{id:"specifying-an-identity-provider",children:"Specifying an identity provider"}),"\n",(0,t.jsxs)(n.p,{children:["By default, LmcRbacMvc internally uses the ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," service key to retrieve the user (logged or\nnot). Therefore, you must implement and register this service in your application by adding these lines in your ",(0,t.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'service_manager' => [\n 'factories' => [\n\t 'Laminas\\Authentication\\AuthenticationService' => function($sm) {\n\t // Create your authentication service!\n\t }\n\t ]\n ]\n];\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"tip",children:(0,t.jsxs)(n.p,{children:["If you are also using the ",(0,t.jsx)(n.a,{href:"https://github.com/lm-commons/lmcuser",children:"LmcUser"})," package, then the ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," will be provided for you and there is no need to implement your own."]})}),"\n",(0,t.jsxs)(n.p,{children:["The identity given by ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," must implement ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Identity\\IdentityInterface"}),"."]}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsx)(n.p,{children:"Note that the default identity provided with Laminas does not implement this interface, neither does the LmcUser suite."})}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc is flexible enough to use something other than the built-in ",(0,t.jsx)(n.code,{children:"AuthenticationService"}),", by specifying custom\nidentity providers. For more information, refer ",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/role-providers#identity-providers",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-guard",children:"Adding a guard"}),"\n",(0,t.jsxs)(n.p,{children:["A guard allows your application to block access to routes and/or controllers using a simple syntax. For instance, this configuration\ngrants access to any route that begins with ",(0,t.jsx)(n.code,{children:"admin"})," (or is exactly ",(0,t.jsx)(n.code,{children:"admin"}),") to the ",(0,t.jsx)(n.code,{children:"admin"})," role only:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n\t 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin']\n\t ]\n ]\n ]\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc has several built-in guards, and you can also register your own guards. For more information, refer\n",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/guards#built-in-guards",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-role-provider",children:"Adding a role provider"}),"\n",(0,t.jsx)(n.p,{children:"RBAC model is based on roles. Therefore, for LmcRbacMvc to work properly, it must be aware of all the roles that are\nused inside your application."}),"\n",(0,t.jsxs)(n.p,{children:["This configuration creates an ",(0,t.jsx)(n.em,{children:"admin"})," role that has a child role called ",(0,t.jsx)(n.em,{children:"member"}),". The ",(0,t.jsx)(n.em,{children:"admin"})," role automatically\ninherits the ",(0,t.jsx)(n.em,{children:"member"})," permissions."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n\t 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n\t 'admin' => [\n\t 'children' => ['member'],\n\t 'permissions' => ['delete']\n\t ],\n\t\t 'member' => [\n\t\t 'permissions' => ['edit']\n\t\t ]\n\t ]\n\t ]\n ]\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["In this example, the ",(0,t.jsx)(n.em,{children:"admin"})," role has two permissions: ",(0,t.jsx)(n.code,{children:"delete"})," and ",(0,t.jsx)(n.code,{children:"edit"})," (because it inherits the permissions from\nits child), while the ",(0,t.jsx)(n.em,{children:"member"})," role only has the ",(0,t.jsx)(n.code,{children:"edit"})," permission."]}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc has several built-in role providers, and you can also register your own role providers. For more information,\nrefer ",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/role-providers#built-in-role-providers",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"registering-a-strategy",children:"Registering a strategy"}),"\n",(0,t.jsxs)(n.p,{children:["When a guard blocks access to a route/controller, or if you throw the ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Exception\\UnauthorizedException"}),"\nexception in your service, LmcRbacMvc automatically performs some logic for you depending on the view strategy used."]}),"\n",(0,t.jsxs)(n.p,{children:['For instance, if you want LmcRbacMvc to automatically redirect all unauthorized requests to the "login" route, add\nthe following code in the ',(0,t.jsx)(n.code,{children:"onBootstrap"})," method of your ",(0,t.jsx)(n.code,{children:"Module.php"})," class:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\RedirectStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["By default, ",(0,t.jsx)(n.code,{children:"RedirectStrategy"}),' redirects all unauthorized requests to a route named "login" when the user is not connected\nand to a route named "home" when the user is connected. This is, of course, entirely configurable.']}),"\n",(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:["For flexibility purposes, LmcRbacMvc ",(0,t.jsx)(n.strong,{children:"does not"})," register any strategy for you by default!"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For more information about built-in strategies, refer ",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/strategies#built-in-strategies",children:"to this section"}),".\n",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/strategies",children:"to this section"})]}),"\n",(0,t.jsx)(n.h2,{id:"using-the-authorization-service",children:"Using the authorization service"}),"\n",(0,t.jsx)(n.p,{children:"Now that LmcRbacMvc is properly configured, you can inject the authorization service into any class and use it to check\nif the current identity is granted to do something."}),"\n",(0,t.jsxs)(n.p,{children:["The authorization service is registered inside the service manager using the following key: ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Service\\AuthorizationService"}),".\nOnce injected, you can use it as follows:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Exception\\UnauthorizedException;\n\nclass ActionController extends \\Laminas\\Mvc\\Controller\\AbstractActionController {\npublic function delete()\n{\n if (!$this->authorizationService->isGranted('delete')) {\n throw new UnauthorizedException();\n }\n\n // Delete the post\n}\n}\n"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(l,{...e})}):l(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>c});var t=i(6540);const r={},o=t.createContext(r);function s(e){const n=t.useContext(o);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/72e14192.b72de290.js b/assets/js/72e14192.b72de290.js deleted file mode 100644 index cf7e1b1a..00000000 --- a/assets/js/72e14192.b72de290.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[814],{3744:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>c,toc:()=>d});var t=i(4848),r=i(8453);const o={sidebar_position:3},s="Quick Start",c={id:"quick-start",title:"Quick Start",description:"In this section, you will learn:",source:"@site/docs/quick-start.md",sourceDirName:".",slug:"/quick-start",permalink:"/lmcrbacmvc/docs/quick-start",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/quick-start.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Requirements and Installation",permalink:"/lmcrbacmvc/docs/installation"},next:{title:"Role providers",permalink:"/lmcrbacmvc/docs/role-providers"}},a={},d=[{value:"Specifying an identity provider",id:"specifying-an-identity-provider",level:2},{value:"Adding a guard",id:"adding-a-guard",level:2},{value:"Adding a role provider",id:"adding-a-role-provider",level:2},{value:"Registering a strategy",id:"registering-a-strategy",level:2},{value:"Using the authorization service",id:"using-the-authorization-service",level:2}];function l(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"quick-start",children:"Quick Start"}),"\n",(0,t.jsx)(n.p,{children:"In this section, you will learn:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"How to set up the module"}),"\n",(0,t.jsx)(n.li,{children:"How to specify an identity provider"}),"\n",(0,t.jsx)(n.li,{children:"How to add a simple role provider"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Before starting the quick start, make sure you have properly installed the module by following the instructions in\nthe README file."}),"\n",(0,t.jsx)(n.h2,{id:"specifying-an-identity-provider",children:"Specifying an identity provider"}),"\n",(0,t.jsxs)(n.p,{children:["By default, LmcRbacMvc internally uses the ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," service key to retrieve the user (logged or\nnot). Therefore, you must implement and register this service in your application by adding these lines in your ",(0,t.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'service_manager' => [\n 'factories' => [\n\t 'Laminas\\Authentication\\AuthenticationService' => function($sm) {\n\t // Create your authentication service!\n\t }\n\t ]\n ]\n];\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"tip",children:(0,t.jsxs)(n.p,{children:["If you are also using the ",(0,t.jsx)(n.a,{href:"https://github.com/lm-commons/lmcuser",children:"LmcUser"})," package, then the ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," will be provided for you and there is no need to implement your own."]})}),"\n",(0,t.jsxs)(n.p,{children:["The identity given by ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," must implement ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Identity\\IdentityInterface"}),"."]}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsx)(n.p,{children:"Note that the default identity provided with Laminas does not implement this interface, neither does the LmcUser suite."})}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc is flexible enough to use something other than the built-in ",(0,t.jsx)(n.code,{children:"AuthenticationService"}),", by specifying custom\nidentity providers. For more information, refer ",(0,t.jsx)(n.a,{href:"/lmcrbacmvc/docs/role-providers#identity-providers",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-guard",children:"Adding a guard"}),"\n",(0,t.jsxs)(n.p,{children:["A guard allows your application to block access to routes and/or controllers using a simple syntax. For instance, this configuration\ngrants access to any route that begins with ",(0,t.jsx)(n.code,{children:"admin"})," (or is exactly ",(0,t.jsx)(n.code,{children:"admin"}),") to the ",(0,t.jsx)(n.code,{children:"admin"})," role only:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n\t 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin']\n\t ]\n ]\n ]\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc has several built-in guards, and you can also register your own guards. For more information, refer\n",(0,t.jsx)(n.a,{href:"/lmcrbacmvc/docs/guards#built-in-guards",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-role-provider",children:"Adding a role provider"}),"\n",(0,t.jsx)(n.p,{children:"RBAC model is based on roles. Therefore, for LmcRbacMvc to work properly, it must be aware of all the roles that are\nused inside your application."}),"\n",(0,t.jsxs)(n.p,{children:["This configuration creates an ",(0,t.jsx)(n.em,{children:"admin"})," role that has a child role called ",(0,t.jsx)(n.em,{children:"member"}),". The ",(0,t.jsx)(n.em,{children:"admin"})," role automatically\ninherits the ",(0,t.jsx)(n.em,{children:"member"})," permissions."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n\t 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n\t 'admin' => [\n\t 'children' => ['member'],\n\t 'permissions' => ['delete']\n\t ],\n\t\t 'member' => [\n\t\t 'permissions' => ['edit']\n\t\t ]\n\t ]\n\t ]\n ]\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["In this example, the ",(0,t.jsx)(n.em,{children:"admin"})," role has two permissions: ",(0,t.jsx)(n.code,{children:"delete"})," and ",(0,t.jsx)(n.code,{children:"edit"})," (because it inherits the permissions from\nits child), while the ",(0,t.jsx)(n.em,{children:"member"})," role only has the ",(0,t.jsx)(n.code,{children:"edit"})," permission."]}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc has several built-in role providers, and you can also register your own role providers. For more information,\nrefer ",(0,t.jsx)(n.a,{href:"/lmcrbacmvc/docs/role-providers#built-in-role-providers",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"registering-a-strategy",children:"Registering a strategy"}),"\n",(0,t.jsxs)(n.p,{children:["When a guard blocks access to a route/controller, or if you throw the ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Exception\\UnauthorizedException"}),"\nexception in your service, LmcRbacMvc automatically performs some logic for you depending on the view strategy used."]}),"\n",(0,t.jsxs)(n.p,{children:['For instance, if you want LmcRbacMvc to automatically redirect all unauthorized requests to the "login" route, add\nthe following code in the ',(0,t.jsx)(n.code,{children:"onBootstrap"})," method of your ",(0,t.jsx)(n.code,{children:"Module.php"})," class:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\RedirectStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["By default, ",(0,t.jsx)(n.code,{children:"RedirectStrategy"}),' redirects all unauthorized requests to a route named "login" when the user is not connected\nand to a route named "home" when the user is connected. This is, of course, entirely configurable.']}),"\n",(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:["For flexibility purposes, LmcRbacMvc ",(0,t.jsx)(n.strong,{children:"does not"})," register any strategy for you by default!"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For more information about built-in strategies, refer ",(0,t.jsx)(n.a,{href:"/lmcrbacmvc/docs/strategies#built-in-strategies",children:"to this section"}),".\n",(0,t.jsx)(n.a,{href:"/lmcrbacmvc/docs/strategies",children:"to this section"})]}),"\n",(0,t.jsx)(n.h2,{id:"using-the-authorization-service",children:"Using the authorization service"}),"\n",(0,t.jsx)(n.p,{children:"Now that LmcRbacMvc is properly configured, you can inject the authorization service into any class and use it to check\nif the current identity is granted to do something."}),"\n",(0,t.jsxs)(n.p,{children:["The authorization service is registered inside the service manager using the following key: ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Service\\AuthorizationService"}),".\nOnce injected, you can use it as follows:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Exception\\UnauthorizedException;\n\nclass ActionController extends \\Laminas\\Mvc\\Controller\\AbstractActionController {\npublic function delete()\n{\n if (!$this->authorizationService->isGranted('delete')) {\n throw new UnauthorizedException();\n }\n\n // Delete the post\n}\n}\n"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(l,{...e})}):l(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>c});var t=i(6540);const r={},o=t.createContext(r);function s(e){const n=t.useContext(o);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/809dac3e.133b40d5.js b/assets/js/809dac3e.133b40d5.js new file mode 100644 index 00000000..3504dd59 --- /dev/null +++ b/assets/js/809dac3e.133b40d5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[804],{4e3:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>i,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>m,toc:()=>s});var c=n(4848),o=n(8453);const a={slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},r=void 0,m={permalink:"/lmc-rbac-mvc/blog/new-documentation",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md",source:"@site/blog/2024-02-22-New-documentation.md",title:"New documentation",description:"This the new documentation site dedicated to the LmcRbacMvc module.",date:"2024-02-22T00:00:00.000Z",formattedDate:"February 22, 2024",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"},{label:"lmcrbacmvc",permalink:"/lmc-rbac-mvc/blog/tags/lmcrbacmvc"}],readingTime:.11,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},unlisted:!1,nextItem:{title:"Welcome",permalink:"/lmc-rbac-mvc/blog/welcome"}},i={authorsImageUrls:[void 0]},s=[];function l(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(e.p,{children:"This the new documentation site dedicated to the LmcRbacMvc module."}),"\n",(0,c.jsx)(e.p,{children:"There are no changes to the code, just improvements in the documentation."})]})}function u(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,c.jsx)(e,{...t,children:(0,c.jsx)(l,{...t})}):l(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>m});var c=n(6540);const o={},a=c.createContext(o);function r(t){const e=c.useContext(a);return c.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function m(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),c.createElement(a.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/809dac3e.241f5d58.js b/assets/js/809dac3e.241f5d58.js deleted file mode 100644 index 38c32708..00000000 --- a/assets/js/809dac3e.241f5d58.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[804],{4e3:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>i,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>m,toc:()=>s});var c=n(4848),o=n(8453);const a={slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},r=void 0,m={permalink:"/lmcrbacmvc/blog/new-documentation",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md",source:"@site/blog/2024-02-22-New-documentation.md",title:"New documentation",description:"This the new documentation site dedicated to the LmcRbacMvc module.",date:"2024-02-22T00:00:00.000Z",formattedDate:"February 22, 2024",tags:[{label:"laminas",permalink:"/lmcrbacmvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmcrbacmvc/blog/tags/php"},{label:"lmcrbacmvc",permalink:"/lmcrbacmvc/blog/tags/lmcrbacmvc"}],readingTime:.11,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},unlisted:!1,nextItem:{title:"Welcome",permalink:"/lmcrbacmvc/blog/welcome"}},i={authorsImageUrls:[void 0]},s=[];function l(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(e.p,{children:"This the new documentation site dedicated to the LmcRbacMvc module."}),"\n",(0,c.jsx)(e.p,{children:"There are no changes to the code, just improvements in the documentation."})]})}function u(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,c.jsx)(e,{...t,children:(0,c.jsx)(l,{...t})}):l(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>m});var c=n(6540);const o={},a=c.createContext(o);function r(t){const e=c.useContext(a);return c.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function m(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),c.createElement(a.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/811b3192.52320e73.js b/assets/js/811b3192.52320e73.js deleted file mode 100644 index 6d37b489..00000000 --- a/assets/js/811b3192.52320e73.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[750],{833:e=>{e.exports=JSON.parse('{"permalink":"/lmcrbacmvc/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/814f3328.84d93b5c.js b/assets/js/814f3328.84d93b5c.js deleted file mode 100644 index 32074353..00000000 --- a/assets/js/814f3328.84d93b5c.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"New documentation","permalink":"/lmcrbacmvc/blog/new-documentation","unlisted":false},{"title":"Welcome","permalink":"/lmcrbacmvc/blog/welcome","unlisted":false}]}')}}]); \ No newline at end of file diff --git a/assets/js/814f3328.d5988fa5.js b/assets/js/814f3328.d5988fa5.js new file mode 100644 index 00000000..6eed08f6 --- /dev/null +++ b/assets/js/814f3328.d5988fa5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"New documentation","permalink":"/lmc-rbac-mvc/blog/new-documentation","unlisted":false},{"title":"Welcome","permalink":"/lmc-rbac-mvc/blog/welcome","unlisted":false}]}')}}]); \ No newline at end of file diff --git a/assets/js/861ecc09.2935ea68.js b/assets/js/861ecc09.2935ea68.js deleted file mode 100644 index e7b23e2b..00000000 --- a/assets/js/861ecc09.2935ea68.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[700],{9012:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"new-documentation","metadata":{"permalink":"/lmcrbacmvc/blog/new-documentation","editUrl":"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md","source":"@site/blog/2024-02-22-New-documentation.md","title":"New documentation","description":"This the new documentation site dedicated to the LmcRbacMvc module.","date":"2024-02-22T00:00:00.000Z","formattedDate":"February 22, 2024","tags":[{"label":"laminas","permalink":"/lmcrbacmvc/blog/tags/laminas"},{"label":"PHP","permalink":"/lmcrbacmvc/blog/tags/php"},{"label":"lmcrbacmvc","permalink":"/lmcrbacmvc/blog/tags/lmcrbacmvc"}],"readingTime":0.11,"hasTruncateMarker":false,"authors":[{"name":"Eric Richer","title":"LM-Commons Administrator","url":"https://github.com/visto9259","imageURL":"https://github.com/visto9259.png","key":"ericr"}],"frontMatter":{"slug":"new-documentation","title":"New documentation","authors":["ericr"],"tags":["laminas","PHP","lmcrbacmvc"]},"unlisted":false,"nextItem":{"title":"Welcome","permalink":"/lmcrbacmvc/blog/welcome"}},"content":"This the new documentation site dedicated to the LmcRbacMvc module.\\n\\nThere are no changes to the code, just improvements in the documentation."},{"id":"welcome","metadata":{"permalink":"/lmcrbacmvc/blog/welcome","editUrl":"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md","source":"@site/blog/2022-08-02-welcome.md","title":"Welcome","description":"Welcome to the new documentation website for the LM-Commons organization.","date":"2022-08-02T00:00:00.000Z","formattedDate":"August 2, 2022","tags":[{"label":"laminas","permalink":"/lmcrbacmvc/blog/tags/laminas"},{"label":"PHP","permalink":"/lmcrbacmvc/blog/tags/php"}],"readingTime":0.155,"hasTruncateMarker":false,"authors":[{"name":"Eric Richer","title":"LM-Commons Administrator","url":"https://github.com/visto9259","imageURL":"https://github.com/visto9259.png","key":"ericr"}],"frontMatter":{"slug":"welcome","title":"Welcome","authors":["ericr"],"tags":["laminas","PHP"]},"unlisted":false,"prevItem":{"title":"New documentation","permalink":"/lmcrbacmvc/blog/new-documentation"}},"content":"Welcome to the new documentation website for the LM-Commons organization.\\n\\nThis site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."}]}')}}]); \ No newline at end of file diff --git a/assets/js/8b5d4ccc.1ace6756.js b/assets/js/8b5d4ccc.1ace6756.js new file mode 100644 index 00000000..19d23aef --- /dev/null +++ b/assets/js/8b5d4ccc.1ace6756.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[604],{82:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>i,contentTitle:()=>a,default:()=>d,frontMatter:()=>t,metadata:()=>l,toc:()=>A});var s=r(4848),o=r(8453);const t={sidebar_position:5},a="Guards",l={id:"guards",title:"Guards",description:"In this section, you will learn:",source:"@site/docs/guards.md",sourceDirName:".",slug:"/guards",permalink:"/lmc-rbac-mvc/docs/guards",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/guards.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Role providers",permalink:"/lmc-rbac-mvc/docs/role-providers"},next:{title:"Strategies",permalink:"/lmc-rbac-mvc/docs/strategies"}},i={},A=[{value:"What are guards and when should you use them?",id:"what-are-guards-and-when-should-you-use-them",level:2},{value:"Protection policy",id:"protection-policy",level:3},{value:"Built-in guards",id:"built-in-guards",level:2},{value:"RouteGuard",id:"routeguard",level:3},{value:"RoutePermissionsGuard",id:"routepermissionsguard",level:3},{value:"ControllerGuard",id:"controllerguard",level:3},{value:"ControllerPermissionsGuard",id:"controllerpermissionsguard",level:3},{value:"Security notice",id:"security-notice",level:3},{value:"Creating custom guards",id:"creating-custom-guards",level:2}];function c(e){const n={blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"guards",children:"Guards"}),"\n",(0,s.jsx)(n.p,{children:"In this section, you will learn:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What guards are"}),"\n",(0,s.jsx)(n.li,{children:"How to use and configure built-in guards"}),"\n",(0,s.jsx)(n.li,{children:"How to create custom guards"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-are-guards-and-when-should-you-use-them",children:"What are guards and when should you use them?"}),"\n",(0,s.jsx)(n.p,{children:"Guards are listeners that are registered on a specific event of\nthe MVC workflow. They allow your application to quickly mark a request as unauthorized."}),"\n",(0,s.jsx)(n.p,{children:"Here is a simple workflow without guards:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Laminas Framework workflow without guards",src:r(4676).A+"",width:"920",height:"200"})}),"\n",(0,s.jsx)(n.p,{children:"And here is a simple workflow with a route guard:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Laminas Framework workflow with guards",src:r(7376).A+"",width:"1120",height:"1600"})}),"\n",(0,s.jsx)(n.p,{children:'RouteGuard and ControllerGuard are not aware of permissions but rather only think about "roles". For\ninstance, you may want to refuse access to each routes that begin by "admin/*" to all users that do not have the\n"admin" role.'}),"\n",(0,s.jsx)(n.p,{children:'If you want to protect a route for a set of permissions, you must use RoutePermissionsGuard. For instance,\nyou may want to grant access to a route "post/delete" only to roles having the "delete" permission.\nNote that in a RBAC system, a permission is linked to a role, not to a user.'}),"\n",(0,s.jsx)(n.p,{children:"Albeit simple to use, guards should not be the only protection in your application, and you should always\nprotect your services as well. The reason is that your business logic should be handled by your service. Protecting a given\nroute or controller does not mean that the service cannot be access from elsewhere (another action for instance)."}),"\n",(0,s.jsx)(n.h3,{id:"protection-policy",children:"Protection policy"}),"\n",(0,s.jsx)(n.p,{children:'By default, when a guard is added, it will perform a check only on the specified guard rules. Any route or controller\nthat is not specified in the rules will be "granted" by default. Therefore, the default is a "blacklist"\nmechanism.'}),"\n",(0,s.jsx)(n.p,{children:'However, you may want a more restrictive approach (also called "whitelist"). In this mode, once a guard is added,\nanything that is not explicitly added will be refused by default.'}),"\n",(0,s.jsx)(n.p,{children:'For instance, let\'s say you have two routes: "index" and "login". If you specify a route guard rule to allow "index"\nroute to "member" role, your "login" route will become defacto unauthorized to anyone, unless you add a new rule for\nallowing the route "login" to "member" role.'}),"\n",(0,s.jsx)(n.p,{children:"You can change it in LmcRbacMvc config, as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Guard\\GuardInterface;\n\nreturn [\n 'lmc_rbac' => [\n 'protection_policy' => GuardInterface::POLICY_DENY\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"NOTE: this policy will block ANY route/controller (so it will also block any console routes or controllers). The\ndeny policy is much more secure, but it needs much more configuration to work with."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"built-in-guards",children:"Built-in guards"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc comes with four guards, in order of priority :"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"RouteGuard : protect a set of routes based on the identity roles"}),"\n",(0,s.jsx)(n.li,{children:"RoutePermissionsGuard : protect a set of routes based on roles permissions"}),"\n",(0,s.jsx)(n.li,{children:"ControllerGuard : protect a controllers and/or actions based on the identity roles"}),"\n",(0,s.jsx)(n.li,{children:"ControllerPermissionsGuard : protect a controllers and/or actions based on roles permissions"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["All guards must be added in the ",(0,s.jsx)(n.code,{children:"guards"})," subkey:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n // Guards config here!\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"Because of the way Laminas Framework handles config, you can without problem define some rules in one module, and\nmore rules in another module. All the rules will be automatically merged."}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:'For your mental health, I recommend you to use either the route guard OR the controller guard, but not both. If\nyou decide to use both conjointly, I recommend you to set the protection policy to "allow" (otherwise, you will\nneed to define rules for every routes AND every controller, which can become quite frustrating!).'}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Please note that if your application uses both route and controller guards, route guards are always executed\n",(0,s.jsx)(n.strong,{children:"before"})," controller guards (they have a higher priority)."]}),"\n",(0,s.jsx)(n.h3,{id:"routeguard",children:"RouteGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The RouteGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -5."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'The RouteGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value",\nwhere the key is a route pattern and the value is an array of role names:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin'],\n 'login' => ['guest']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Only one role in a rule needs to be matched (it is an OR condition)."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to all admin routes to users that have the "admin" role, and grant access to the "login"\nroute to users that have the "guest" role (eg.: most likely unauthenticated users).'}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can also use the wildcard character * for roles:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'home' => ['*']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'This rule grants access to the "home" route to anyone.'}),"\n",(0,s.jsx)(n.p,{children:"Finally, you can also omit the roles array to completely block a route, for maintenance purpose for example :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'route_under_construction'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"This rule will be inaccessible."}),"\n",(0,s.jsx)(n.p,{children:"Note : this last example could be (and should be) written in a more explicit way :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'route_under_construction' => []\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.h3,{id:"routepermissionsguard",children:"RoutePermissionsGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The RoutePermissionsGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -8."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'The RoutePermissionsGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value",\nwhere the key is a route pattern and the value is an array of permission names:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'admin*' => ['admin'],\n 'post/manage' => ['post.update', 'post.delete']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"By default, all permissions in a rule must be matched (an AND condition)."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["In the previous example, one must have ",(0,s.jsx)(n.code,{children:"post.update"})," ",(0,s.jsx)(n.strong,{children:"AND"})," ",(0,s.jsx)(n.code,{children:"post.delete"})," permissions\nto access the ",(0,s.jsx)(n.code,{children:"post/manage"})," route. You can also specify an OR condition like so:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Guard\\GuardInterface;\n\nreturn [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'post/manage' => [\n 'permissions' => ['post.update', 'post.delete'],\n 'condition' => GuardInterface::CONDITION_OR\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Permissions are linked to roles, not to users"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to all admin routes to roles that have the "admin" permission, and grant access to the\n"post/delete" route to roles that have the "post.delete" or "admin" permissions.'}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can also use the wildcard character * for permissions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'home' => ['*']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'This rule grants access to the "home" route to anyone.'}),"\n",(0,s.jsx)(n.p,{children:"Finally, you can also use an empty array to completly block a route, for maintenance purpose for example :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'route_under_construction' => []\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"This route will be inaccessible."}),"\n",(0,s.jsx)(n.h3,{id:"controllerguard",children:"ControllerGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The ControllerGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -10."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The ControllerGuard allows your application to protect a controller. You must provide an array of arrays:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'MyController',\n 'roles' => ['guest', 'member']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Only one role in a rule need to be matched (it is an OR condition)."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to each actions of the MyController controller to users that have either the "guest" or\n"member" roles.'}),"\n",(0,s.jsx)(n.p,{children:"As for RouteGuard, you can use a wildcard (*) character for roles."}),"\n",(0,s.jsx)(n.p,{children:"You can also specify optional actions, so that the rule only apply to one or several actions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'MyController',\n 'actions' => ['read', 'edit'],\n 'roles' => ['guest', 'member']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"You can combine a generic rule and a specific action rule for the same controller, as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'PostController',\n 'roles' => ['member']\n ],\n [\n 'controller' => 'PostController',\n 'actions' => ['delete'],\n 'roles' => ['admin']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'These rules grant access to each controller action to users that have the "member" role, but restrict the\n"delete" action to "admin" only.'}),"\n",(0,s.jsx)(n.h3,{id:"controllerpermissionsguard",children:"ControllerPermissionsGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The ControllerPermissionsGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -13."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The ControllerPermissionsGuard allows your application to protect a controller using permissions. You must provide an array of arrays:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerPermissionsGuard' => [\n [\n 'controller' => 'MyController',\n 'permissions' => ['post.update', 'post.delete']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"All permissions in a rule must be matched (it is an AND condition)."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["In the previous example, the user must have ",(0,s.jsx)(n.code,{children:"post.update"})," ",(0,s.jsx)(n.strong,{children:"AND"})," ",(0,s.jsx)(n.code,{children:"post.delete"})," permissions\nto access each action of the MyController controller."]}),"\n",(0,s.jsx)(n.p,{children:"As for all other guards, you can use a wildcard (*) character for permissions."}),"\n",(0,s.jsx)(n.p,{children:"The configuration rules are the same as for ControllerGuard."}),"\n",(0,s.jsx)(n.h3,{id:"security-notice",children:"Security notice"}),"\n",(0,s.jsxs)(n.p,{children:["RouteGuard and ControllerGuard listen to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event. Therefore, if you use the\n",(0,s.jsx)(n.code,{children:"forward"})," method in your controller, those guards will not intercept and check requests (because internally\nLaminas MVC does not trigger again a new MVC loop)."]}),"\n",(0,s.jsx)(n.p,{children:"Most of the time, this is not an issue, but you must be aware of it, and this is an additional reason why you\nshould always protect your services too."}),"\n",(0,s.jsx)(n.h2,{id:"creating-custom-guards",children:"Creating custom guards"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc is flexible enough to allow you to create custom guards. Let's say we want to create a guard that will\nrefuse access based on an IP addresses blacklist."}),"\n",(0,s.jsx)(n.p,{children:"First create the guard:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace Application\\Guard;\n\nuse Laminas\\Http\\Request as HttpRequest;\nuse Laminas\\Mvc\\MvcEvent;\nuse LmcRbacMvc\\Guard\\AbstractGuard;\n\nclass IpGuard extends AbstractGuard\n{\n const EVENT_PRIORITY = 100;\n\n /**\n * List of IPs to blacklist\n */\n protected $ipAddresses = [];\n\n /**\n * @param array $ipAddresses\n */\n public function __construct(array $ipAddresses)\n {\n $this->ipAddresses = $ipAddresses;\n }\n\n /**\n * @param MvcEvent $event\n * @return bool\n */\n public function isGranted(MvcEvent $event)\n {\n $request = $event->getRequest();\n\n if (!$request instanceof HttpRequest) {\n return true;\n }\n\n $clientIp = $_SERVER['REMOTE_ADDR'];\n\n return !in_array($clientIp, $this->ipAddresses);\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["Guards must implement ",(0,s.jsx)(n.code,{children:"LmcRbacMvc\\Guard\\GuardInterface"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["By default, guards are listening to the event ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," with a priority of -5 (you can change the default\nevent to listen by overriding the ",(0,s.jsx)(n.code,{children:"EVENT_NAME"})," constant in your guard subclass). However, in this case, we don't\neven need to wait for the route to be matched, so we overload the ",(0,s.jsx)(n.code,{children:"EVENT_PRIORITY"})," constant to be executed earlier."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"isGranted"})," method simply retrieves the client IP address, and checks it against the blacklist."]}),"\n",(0,s.jsx)(n.p,{children:"However, for this to work, we must register the newly created guard with the guard plugin manager. To do so, add the\nfollowing code in your config:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'zfc_rbac' => [\n 'guard_manager' => [\n 'factories' => [\n 'Application\\Guard\\IpGuard' => 'Application\\Factory\\IpGuardFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"guard_manager"})," config follows a conventional service manager configuration format."]}),"\n",(0,s.jsx)(n.p,{children:"Now, let's create the factory:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace Application\\Factory;\n\nuse Application\\Guard\\IpGuard;\nuse Laminas\\ServiceManager\\Factory\\FactoryInterface;\nuse Laminas\\ServiceManager\\ServiceLocatorInterface;\n\nclass IpGuardFactory implements FactoryInterface\n{\n /**\n * {@inheritDoc}\n */\n public function __invoke(ContainerInterface $container, $requestedName, array $options = null)\n {\n if (null === $options) {\n $options = [];\n }\n return new IpGuard($options);\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"In a real use case, you would likely fetched the blacklist from a database."}),"\n",(0,s.jsxs)(n.p,{children:["Now we just need to add the guard to the ",(0,s.jsx)(n.code,{children:"guards"})," option, so that LmcRbacMvc can execute the logic behind this guard. In\nyour config, add the following code:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'Application\\Guard\\IpGuard' => [\n '87.45.66.46',\n '65.87.35.43'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The array of IP addresses will be passed to ",(0,s.jsx)(n.code,{children:"IpGuardFactory::__invoke"})," in the ",(0,s.jsx)(n.code,{children:"$options"})," parameter."]})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},7376:(e,n,r)=>{r.d(n,{A:()=>s});const s=r.p+"assets/images/workflow-with-guards-9421d2ccbded4218039d3c55d9e07d64.png"},4676:(e,n,r)=>{r.d(n,{A:()=>s});const s="data:image/png;base64,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"},8453:(e,n,r)=>{r.d(n,{R:()=>a,x:()=>l});var s=r(6540);const o={},t=s.createContext(o);function a(e){const n=s.useContext(t);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/8b5d4ccc.302b4497.js b/assets/js/8b5d4ccc.302b4497.js deleted file mode 100644 index 1dee4780..00000000 --- a/assets/js/8b5d4ccc.302b4497.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[223],{82:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>i,contentTitle:()=>a,default:()=>d,frontMatter:()=>t,metadata:()=>l,toc:()=>A});var s=r(4848),o=r(8453);const t={sidebar_position:5},a="Guards",l={id:"guards",title:"Guards",description:"In this section, you will learn:",source:"@site/docs/guards.md",sourceDirName:".",slug:"/guards",permalink:"/lmcrbacmvc/docs/guards",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/guards.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Role providers",permalink:"/lmcrbacmvc/docs/role-providers"},next:{title:"Strategies",permalink:"/lmcrbacmvc/docs/strategies"}},i={},A=[{value:"What are guards and when should you use them?",id:"what-are-guards-and-when-should-you-use-them",level:2},{value:"Protection policy",id:"protection-policy",level:3},{value:"Built-in guards",id:"built-in-guards",level:2},{value:"RouteGuard",id:"routeguard",level:3},{value:"RoutePermissionsGuard",id:"routepermissionsguard",level:3},{value:"ControllerGuard",id:"controllerguard",level:3},{value:"ControllerPermissionsGuard",id:"controllerpermissionsguard",level:3},{value:"Security notice",id:"security-notice",level:3},{value:"Creating custom guards",id:"creating-custom-guards",level:2}];function c(e){const n={blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"guards",children:"Guards"}),"\n",(0,s.jsx)(n.p,{children:"In this section, you will learn:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What guards are"}),"\n",(0,s.jsx)(n.li,{children:"How to use and configure built-in guards"}),"\n",(0,s.jsx)(n.li,{children:"How to create custom guards"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-are-guards-and-when-should-you-use-them",children:"What are guards and when should you use them?"}),"\n",(0,s.jsx)(n.p,{children:"Guards are listeners that are registered on a specific event of\nthe MVC workflow. They allow your application to quickly mark a request as unauthorized."}),"\n",(0,s.jsx)(n.p,{children:"Here is a simple workflow without guards:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Laminas Framework workflow without guards",src:r(4676).A+"",width:"920",height:"200"})}),"\n",(0,s.jsx)(n.p,{children:"And here is a simple workflow with a route guard:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Laminas Framework workflow with guards",src:r(7376).A+"",width:"1120",height:"1600"})}),"\n",(0,s.jsx)(n.p,{children:'RouteGuard and ControllerGuard are not aware of permissions but rather only think about "roles". For\ninstance, you may want to refuse access to each routes that begin by "admin/*" to all users that do not have the\n"admin" role.'}),"\n",(0,s.jsx)(n.p,{children:'If you want to protect a route for a set of permissions, you must use RoutePermissionsGuard. For instance,\nyou may want to grant access to a route "post/delete" only to roles having the "delete" permission.\nNote that in a RBAC system, a permission is linked to a role, not to a user.'}),"\n",(0,s.jsx)(n.p,{children:"Albeit simple to use, guards should not be the only protection in your application, and you should always\nprotect your services as well. The reason is that your business logic should be handled by your service. Protecting a given\nroute or controller does not mean that the service cannot be access from elsewhere (another action for instance)."}),"\n",(0,s.jsx)(n.h3,{id:"protection-policy",children:"Protection policy"}),"\n",(0,s.jsx)(n.p,{children:'By default, when a guard is added, it will perform a check only on the specified guard rules. Any route or controller\nthat is not specified in the rules will be "granted" by default. Therefore, the default is a "blacklist"\nmechanism.'}),"\n",(0,s.jsx)(n.p,{children:'However, you may want a more restrictive approach (also called "whitelist"). In this mode, once a guard is added,\nanything that is not explicitly added will be refused by default.'}),"\n",(0,s.jsx)(n.p,{children:'For instance, let\'s say you have two routes: "index" and "login". If you specify a route guard rule to allow "index"\nroute to "member" role, your "login" route will become defacto unauthorized to anyone, unless you add a new rule for\nallowing the route "login" to "member" role.'}),"\n",(0,s.jsx)(n.p,{children:"You can change it in LmcRbacMvc config, as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Guard\\GuardInterface;\n\nreturn [\n 'lmc_rbac' => [\n 'protection_policy' => GuardInterface::POLICY_DENY\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"NOTE: this policy will block ANY route/controller (so it will also block any console routes or controllers). The\ndeny policy is much more secure, but it needs much more configuration to work with."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"built-in-guards",children:"Built-in guards"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc comes with four guards, in order of priority :"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"RouteGuard : protect a set of routes based on the identity roles"}),"\n",(0,s.jsx)(n.li,{children:"RoutePermissionsGuard : protect a set of routes based on roles permissions"}),"\n",(0,s.jsx)(n.li,{children:"ControllerGuard : protect a controllers and/or actions based on the identity roles"}),"\n",(0,s.jsx)(n.li,{children:"ControllerPermissionsGuard : protect a controllers and/or actions based on roles permissions"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["All guards must be added in the ",(0,s.jsx)(n.code,{children:"guards"})," subkey:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n // Guards config here!\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"Because of the way Laminas Framework handles config, you can without problem define some rules in one module, and\nmore rules in another module. All the rules will be automatically merged."}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:'For your mental health, I recommend you to use either the route guard OR the controller guard, but not both. If\nyou decide to use both conjointly, I recommend you to set the protection policy to "allow" (otherwise, you will\nneed to define rules for every routes AND every controller, which can become quite frustrating!).'}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Please note that if your application uses both route and controller guards, route guards are always executed\n",(0,s.jsx)(n.strong,{children:"before"})," controller guards (they have a higher priority)."]}),"\n",(0,s.jsx)(n.h3,{id:"routeguard",children:"RouteGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The RouteGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -5."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'The RouteGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value",\nwhere the key is a route pattern and the value is an array of role names:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin'],\n 'login' => ['guest']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Only one role in a rule needs to be matched (it is an OR condition)."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to all admin routes to users that have the "admin" role, and grant access to the "login"\nroute to users that have the "guest" role (eg.: most likely unauthenticated users).'}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can also use the wildcard character * for roles:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'home' => ['*']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'This rule grants access to the "home" route to anyone.'}),"\n",(0,s.jsx)(n.p,{children:"Finally, you can also omit the roles array to completely block a route, for maintenance purpose for example :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'route_under_construction'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"This rule will be inaccessible."}),"\n",(0,s.jsx)(n.p,{children:"Note : this last example could be (and should be) written in a more explicit way :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'route_under_construction' => []\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.h3,{id:"routepermissionsguard",children:"RoutePermissionsGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The RoutePermissionsGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -8."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'The RoutePermissionsGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value",\nwhere the key is a route pattern and the value is an array of permission names:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'admin*' => ['admin'],\n 'post/manage' => ['post.update', 'post.delete']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"By default, all permissions in a rule must be matched (an AND condition)."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["In the previous example, one must have ",(0,s.jsx)(n.code,{children:"post.update"})," ",(0,s.jsx)(n.strong,{children:"AND"})," ",(0,s.jsx)(n.code,{children:"post.delete"})," permissions\nto access the ",(0,s.jsx)(n.code,{children:"post/manage"})," route. You can also specify an OR condition like so:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Guard\\GuardInterface;\n\nreturn [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'post/manage' => [\n 'permissions' => ['post.update', 'post.delete'],\n 'condition' => GuardInterface::CONDITION_OR\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Permissions are linked to roles, not to users"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to all admin routes to roles that have the "admin" permission, and grant access to the\n"post/delete" route to roles that have the "post.delete" or "admin" permissions.'}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can also use the wildcard character * for permissions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'home' => ['*']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'This rule grants access to the "home" route to anyone.'}),"\n",(0,s.jsx)(n.p,{children:"Finally, you can also use an empty array to completly block a route, for maintenance purpose for example :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'route_under_construction' => []\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"This route will be inaccessible."}),"\n",(0,s.jsx)(n.h3,{id:"controllerguard",children:"ControllerGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The ControllerGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -10."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The ControllerGuard allows your application to protect a controller. You must provide an array of arrays:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'MyController',\n 'roles' => ['guest', 'member']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Only one role in a rule need to be matched (it is an OR condition)."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to each actions of the MyController controller to users that have either the "guest" or\n"member" roles.'}),"\n",(0,s.jsx)(n.p,{children:"As for RouteGuard, you can use a wildcard (*) character for roles."}),"\n",(0,s.jsx)(n.p,{children:"You can also specify optional actions, so that the rule only apply to one or several actions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'MyController',\n 'actions' => ['read', 'edit'],\n 'roles' => ['guest', 'member']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"You can combine a generic rule and a specific action rule for the same controller, as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'PostController',\n 'roles' => ['member']\n ],\n [\n 'controller' => 'PostController',\n 'actions' => ['delete'],\n 'roles' => ['admin']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'These rules grant access to each controller action to users that have the "member" role, but restrict the\n"delete" action to "admin" only.'}),"\n",(0,s.jsx)(n.h3,{id:"controllerpermissionsguard",children:"ControllerPermissionsGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The ControllerPermissionsGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -13."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The ControllerPermissionsGuard allows your application to protect a controller using permissions. You must provide an array of arrays:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerPermissionsGuard' => [\n [\n 'controller' => 'MyController',\n 'permissions' => ['post.update', 'post.delete']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"All permissions in a rule must be matched (it is an AND condition)."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["In the previous example, the user must have ",(0,s.jsx)(n.code,{children:"post.update"})," ",(0,s.jsx)(n.strong,{children:"AND"})," ",(0,s.jsx)(n.code,{children:"post.delete"})," permissions\nto access each action of the MyController controller."]}),"\n",(0,s.jsx)(n.p,{children:"As for all other guards, you can use a wildcard (*) character for permissions."}),"\n",(0,s.jsx)(n.p,{children:"The configuration rules are the same as for ControllerGuard."}),"\n",(0,s.jsx)(n.h3,{id:"security-notice",children:"Security notice"}),"\n",(0,s.jsxs)(n.p,{children:["RouteGuard and ControllerGuard listen to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event. Therefore, if you use the\n",(0,s.jsx)(n.code,{children:"forward"})," method in your controller, those guards will not intercept and check requests (because internally\nLaminas MVC does not trigger again a new MVC loop)."]}),"\n",(0,s.jsx)(n.p,{children:"Most of the time, this is not an issue, but you must be aware of it, and this is an additional reason why you\nshould always protect your services too."}),"\n",(0,s.jsx)(n.h2,{id:"creating-custom-guards",children:"Creating custom guards"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc is flexible enough to allow you to create custom guards. Let's say we want to create a guard that will\nrefuse access based on an IP addresses blacklist."}),"\n",(0,s.jsx)(n.p,{children:"First create the guard:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace Application\\Guard;\n\nuse Laminas\\Http\\Request as HttpRequest;\nuse Laminas\\Mvc\\MvcEvent;\nuse LmcRbacMvc\\Guard\\AbstractGuard;\n\nclass IpGuard extends AbstractGuard\n{\n const EVENT_PRIORITY = 100;\n\n /**\n * List of IPs to blacklist\n */\n protected $ipAddresses = [];\n\n /**\n * @param array $ipAddresses\n */\n public function __construct(array $ipAddresses)\n {\n $this->ipAddresses = $ipAddresses;\n }\n\n /**\n * @param MvcEvent $event\n * @return bool\n */\n public function isGranted(MvcEvent $event)\n {\n $request = $event->getRequest();\n\n if (!$request instanceof HttpRequest) {\n return true;\n }\n\n $clientIp = $_SERVER['REMOTE_ADDR'];\n\n return !in_array($clientIp, $this->ipAddresses);\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["Guards must implement ",(0,s.jsx)(n.code,{children:"LmcRbacMvc\\Guard\\GuardInterface"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["By default, guards are listening to the event ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," with a priority of -5 (you can change the default\nevent to listen by overriding the ",(0,s.jsx)(n.code,{children:"EVENT_NAME"})," constant in your guard subclass). However, in this case, we don't\neven need to wait for the route to be matched, so we overload the ",(0,s.jsx)(n.code,{children:"EVENT_PRIORITY"})," constant to be executed earlier."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"isGranted"})," method simply retrieves the client IP address, and checks it against the blacklist."]}),"\n",(0,s.jsx)(n.p,{children:"However, for this to work, we must register the newly created guard with the guard plugin manager. To do so, add the\nfollowing code in your config:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'zfc_rbac' => [\n 'guard_manager' => [\n 'factories' => [\n 'Application\\Guard\\IpGuard' => 'Application\\Factory\\IpGuardFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"guard_manager"})," config follows a conventional service manager configuration format."]}),"\n",(0,s.jsx)(n.p,{children:"Now, let's create the factory:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace Application\\Factory;\n\nuse Application\\Guard\\IpGuard;\nuse Laminas\\ServiceManager\\Factory\\FactoryInterface;\nuse Laminas\\ServiceManager\\ServiceLocatorInterface;\n\nclass IpGuardFactory implements FactoryInterface\n{\n /**\n * {@inheritDoc}\n */\n public function __invoke(ContainerInterface $container, $requestedName, array $options = null)\n {\n if (null === $options) {\n $options = [];\n }\n return new IpGuard($options);\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"In a real use case, you would likely fetched the blacklist from a database."}),"\n",(0,s.jsxs)(n.p,{children:["Now we just need to add the guard to the ",(0,s.jsx)(n.code,{children:"guards"})," option, so that LmcRbacMvc can execute the logic behind this guard. In\nyour config, add the following code:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'Application\\Guard\\IpGuard' => [\n '87.45.66.46',\n '65.87.35.43'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The array of IP addresses will be passed to ",(0,s.jsx)(n.code,{children:"IpGuardFactory::__invoke"})," in the ",(0,s.jsx)(n.code,{children:"$options"})," parameter."]})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},7376:(e,n,r)=>{r.d(n,{A:()=>s});const s=r.p+"assets/images/workflow-with-guards-9421d2ccbded4218039d3c55d9e07d64.png"},4676:(e,n,r)=>{r.d(n,{A:()=>s});const s="data:image/png;base64,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"},8453:(e,n,r)=>{r.d(n,{R:()=>a,x:()=>l});var s=r(6540);const o={},t=s.createContext(o);function a(e){const n=s.useContext(t);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.6562d556.js b/assets/js/935f2afb.6562d556.js new file mode 100644 index 00000000..3f33b336 --- /dev/null +++ b/assets/js/935f2afb.6562d556.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[581],{5610:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"Introduction","href":"/lmc-rbac-mvc/docs/intro","docId":"intro","unlisted":false},{"type":"link","label":"Requirements and Installation","href":"/lmc-rbac-mvc/docs/installation","docId":"installation","unlisted":false},{"type":"link","label":"Quick Start","href":"/lmc-rbac-mvc/docs/quick-start","docId":"quick-start","unlisted":false},{"type":"link","label":"Role providers","href":"/lmc-rbac-mvc/docs/role-providers","docId":"role-providers","unlisted":false},{"type":"link","label":"Guards","href":"/lmc-rbac-mvc/docs/guards","docId":"guards","unlisted":false},{"type":"link","label":"Strategies","href":"/lmc-rbac-mvc/docs/strategies","docId":"strategies","unlisted":false},{"type":"link","label":"Using the Authorization Service","href":"/lmc-rbac-mvc/docs/using-the-authorization-service","docId":"using-the-authorization-service","unlisted":false},{"type":"link","label":"Cookbook","href":"/lmc-rbac-mvc/docs/cookbook","docId":"cookbook","unlisted":false},{"type":"link","label":"Support","href":"/lmc-rbac-mvc/docs/support","docId":"support","unlisted":false}]},"docs":{"cookbook":{"id":"cookbook","title":"Cookbook","description":"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have","sidebar":"tutorialSidebar"},"guards":{"id":"guards","title":"Guards","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"installation":{"id":"installation","title":"Requirements and Installation","description":"Requirements","sidebar":"tutorialSidebar"},"intro":{"id":"intro","title":"Introduction","description":"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\\\Permissions\\\\Rbac","sidebar":"tutorialSidebar"},"quick-start":{"id":"quick-start","title":"Quick Start","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"role-providers":{"id":"role-providers","title":"Role providers","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"strategies":{"id":"strategies","title":"Strategies","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"support":{"id":"support","title":"Support","description":"- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues.","sidebar":"tutorialSidebar"},"using-the-authorization-service":{"id":"using-the-authorization-service","title":"Using the Authorization Service","description":"This section will teach you how to use the AuthorizationService to its full extent.","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.e416ff70.js b/assets/js/935f2afb.e416ff70.js deleted file mode 100644 index eac17b35..00000000 --- a/assets/js/935f2afb.e416ff70.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[581],{5610:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"Introduction","href":"/lmcrbacmvc/docs/intro","docId":"intro","unlisted":false},{"type":"link","label":"Requirements and Installation","href":"/lmcrbacmvc/docs/installation","docId":"installation","unlisted":false},{"type":"link","label":"Quick Start","href":"/lmcrbacmvc/docs/quick-start","docId":"quick-start","unlisted":false},{"type":"link","label":"Role providers","href":"/lmcrbacmvc/docs/role-providers","docId":"role-providers","unlisted":false},{"type":"link","label":"Guards","href":"/lmcrbacmvc/docs/guards","docId":"guards","unlisted":false},{"type":"link","label":"Strategies","href":"/lmcrbacmvc/docs/strategies","docId":"strategies","unlisted":false},{"type":"link","label":"Using the Authorization Service","href":"/lmcrbacmvc/docs/using-the-authorization-service","docId":"using-the-authorization-service","unlisted":false},{"type":"link","label":"Cookbook","href":"/lmcrbacmvc/docs/cookbook","docId":"cookbook","unlisted":false},{"type":"link","label":"Support","href":"/lmcrbacmvc/docs/support","docId":"support","unlisted":false}]},"docs":{"cookbook":{"id":"cookbook","title":"Cookbook","description":"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have","sidebar":"tutorialSidebar"},"guards":{"id":"guards","title":"Guards","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"installation":{"id":"installation","title":"Requirements and Installation","description":"Requirements","sidebar":"tutorialSidebar"},"intro":{"id":"intro","title":"Introduction","description":"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\\\Permissions\\\\Rbac","sidebar":"tutorialSidebar"},"quick-start":{"id":"quick-start","title":"Quick Start","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"role-providers":{"id":"role-providers","title":"Role providers","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"strategies":{"id":"strategies","title":"Strategies","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"support":{"id":"support","title":"Support","description":"- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues.","sidebar":"tutorialSidebar"},"using-the-authorization-service":{"id":"using-the-authorization-service","title":"Using the Authorization Service","description":"This section will teach you how to use the AuthorizationService to its full extent.","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/93a10038.26f7b70f.js b/assets/js/93a10038.26f7b70f.js new file mode 100644 index 00000000..456d2aeb --- /dev/null +++ b/assets/js/93a10038.26f7b70f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[828],{4738:c=>{c.exports=JSON.parse('{"label":"lmcrbacmvc","permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc","allTagsPath":"/lmc-rbac-mvc/blog/tags","count":1,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/983b303c.20d2d832.js b/assets/js/983b303c.20d2d832.js new file mode 100644 index 00000000..b8e62e06 --- /dev/null +++ b/assets/js/983b303c.20d2d832.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[660],{4009:e=>{e.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/9de7562b.af8b2b3b.js b/assets/js/9de7562b.af8b2b3b.js deleted file mode 100644 index 108389c8..00000000 --- a/assets/js/9de7562b.af8b2b3b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[49],{5330:s=>{s.exports=JSON.parse('{"permalink":"/lmcrbacmvc/blog/tags/laminas","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/a6185ec0.7643b318.js b/assets/js/a6185ec0.7643b318.js new file mode 100644 index 00000000..243142bf --- /dev/null +++ b/assets/js/a6185ec0.7643b318.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[270],{3808:c=>{c.exports=JSON.parse('[{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas","count":2},{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php","count":2},{"label":"lmcrbacmvc","permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/afaab3e7.6e29388c.js b/assets/js/afaab3e7.6e29388c.js deleted file mode 100644 index 3eac18b1..00000000 --- a/assets/js/afaab3e7.6e29388c.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[789],{4261:c=>{c.exports=JSON.parse('{"label":"lmcrbacmvc","permalink":"/lmcrbacmvc/blog/tags/lmcrbacmvc","allTagsPath":"/lmcrbacmvc/blog/tags","count":1,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/b2c2794f.6e28ca28.js b/assets/js/b2c2794f.6e28ca28.js deleted file mode 100644 index 209803d7..00000000 --- a/assets/js/b2c2794f.6e28ca28.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[438],{1085:c=>{c.exports=JSON.parse('[{"label":"laminas","permalink":"/lmcrbacmvc/blog/tags/laminas","count":2},{"label":"PHP","permalink":"/lmcrbacmvc/blog/tags/php","count":2},{"label":"lmcrbacmvc","permalink":"/lmcrbacmvc/blog/tags/lmcrbacmvc","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/b5cb822e.5f84e9da.js b/assets/js/b5cb822e.5f84e9da.js new file mode 100644 index 00000000..9653256c --- /dev/null +++ b/assets/js/b5cb822e.5f84e9da.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[390],{2957:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>m});var n=o(4848),c=o(8453);const r={slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},s=void 0,a={permalink:"/lmc-rbac-mvc/blog/welcome",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md",source:"@site/blog/2022-08-02-welcome.md",title:"Welcome",description:"Welcome to the new documentation website for the LM-Commons organization.",date:"2022-08-02T00:00:00.000Z",formattedDate:"August 2, 2022",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"}],readingTime:.155,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},unlisted:!1,prevItem:{title:"New documentation",permalink:"/lmc-rbac-mvc/blog/new-documentation"}},i={authorsImageUrls:[void 0]},m=[];function l(e){const t={p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.p,{children:"Welcome to the new documentation website for the LM-Commons organization."}),"\n",(0,n.jsx)(t.p,{children:"This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."})]})}function u(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>s,x:()=>a});var n=o(6540);const c={},r=n.createContext(c);function s(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b5cb822e.cb40d46f.js b/assets/js/b5cb822e.cb40d46f.js deleted file mode 100644 index 396a6b94..00000000 --- a/assets/js/b5cb822e.cb40d46f.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[390],{2957:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>m});var n=o(4848),c=o(8453);const r={slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},s=void 0,a={permalink:"/lmcrbacmvc/blog/welcome",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md",source:"@site/blog/2022-08-02-welcome.md",title:"Welcome",description:"Welcome to the new documentation website for the LM-Commons organization.",date:"2022-08-02T00:00:00.000Z",formattedDate:"August 2, 2022",tags:[{label:"laminas",permalink:"/lmcrbacmvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmcrbacmvc/blog/tags/php"}],readingTime:.155,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},unlisted:!1,prevItem:{title:"New documentation",permalink:"/lmcrbacmvc/blog/new-documentation"}},i={authorsImageUrls:[void 0]},m=[];function l(e){const t={p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.p,{children:"Welcome to the new documentation website for the LM-Commons organization."}),"\n",(0,n.jsx)(t.p,{children:"This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."})]})}function u(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>s,x:()=>a});var n=o(6540);const c={},r=n.createContext(c);function s(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b9207088.224f5717.js b/assets/js/b9207088.224f5717.js deleted file mode 100644 index 0b67b53d..00000000 --- a/assets/js/b9207088.224f5717.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[571],{8849:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>s,metadata:()=>c,toc:()=>l});var i=t(4848),o=t(8453);const s={sidebar_position:8},r="Cookbook",c={id:"cookbook",title:"Cookbook",description:"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have",source:"@site/docs/cookbook.md",sourceDirName:".",slug:"/cookbook",permalink:"/lmcrbacmvc/docs/cookbook",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/cookbook.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{sidebar_position:8},sidebar:"tutorialSidebar",previous:{title:"Using the Authorization Service",permalink:"/lmcrbacmvc/docs/using-the-authorization-service"},next:{title:"Support",permalink:"/lmcrbacmvc/docs/support"}},a={},l=[{value:"A Real World Application",id:"a-real-world-application",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"When using guards then?",id:"when-using-guards-then",level:3},{value:"A Real World Application Part 2 - Only delete your own Posts",id:"a-real-world-application-part-2---only-delete-your-own-posts",level:2},{value:"A Real World Application Part 3 - Admins can delete everything",id:"a-real-world-application-part-3---admins-can-delete-everything",level:2},{value:"A Real World Application Part 4 - Checking permissions in the view",id:"a-real-world-application-part-4---checking-permissions-in-the-view",level:2},{value:"Using LmcRbacMvc with Doctrine ORM",id:"using-lmcrbacmvc-with-doctrine-orm",level:2},{value:"How to deal with roles with lot of permissions?",id:"how-to-deal-with-roles-with-lot-of-permissions",level:2},{value:"Using LmcRbacMvc and ZF2 Assetic",id:"using-lmcrbacmvc-and-zf2-assetic",level:2},{value:"Using LmcRbacMvc and LmcUser",id:"using-lmcrbacmvc-and-lmcuser",level:2}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",strong:"strong",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h1,{id:"cookbook",children:"Cookbook"}),"\n",(0,i.jsx)(n.p,{children:"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have\nany other recipe you'd like to add, please open an issue!"}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application",children:"A Real World Application"}),"\n",(0,i.jsxs)(n.p,{children:["In this example we are going to create a very little real world application. We will create a controller\n",(0,i.jsx)(n.code,{children:"PostController"})," that interacts with a service called ",(0,i.jsx)(n.code,{children:"PostService"}),". For the sake of simplicity we will only\ncover the ",(0,i.jsx)(n.code,{children:"delete"}),"-methods of both parts."]}),"\n",(0,i.jsxs)(n.p,{children:["Let's start by creating a controller that has the ",(0,i.jsx)(n.code,{children:"PostService"})," as dependency:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function __construct(PostService $postService)\n {\n $this->postService = $postService;\n }\n\n // addAction(), editAction(), etc...\n\n public function deleteAction()\n {\n $id = $this->params()->fromQuery('id');\n\n $this->postService->deletePost($id);\n\n return $this->redirect()->toRoute('posts');\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Since we have a dependency, let's inject it using the ",(0,i.jsx)(n.code,{children:"ControllerManager"}),", we will do this inside our ",(0,i.jsx)(n.code,{children:"Module"})," class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n public function getConfig()\n {\n return [\n 'controllers' => [\n 'factories' => [\n 'PostController' => function ($cpm) {\n // We assume a Service key 'PostService' here that returns the PostService Class\n return new PostController(\n $cpm->getServiceLocator()->get('PostService')\n );\n },\n ],\n ],\n ];\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now that we have this in place let us quickly define our ",(0,i.jsx)(n.code,{children:"PostService"}),". We will be using a Service that makes use\nof Doctrine, so we require a ",(0,i.jsx)(n.code,{children:"Doctrine\\Persistence\\ObjectManager"})," as dependency."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n public function __construct(ObjectManager $objectManager)\n {\n $this->objectManager = $objectManager;\n }\n\n public function deletePost($id)\n {\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["And for this one, too, let's quickly create the factory, again within our ",(0,i.jsx)(n.code,{children:"Module"})," class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), getConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'PostService' => function($sm) {\n return new PostService(\n $sm->get('doctrine.entitymanager.orm_default')\n );\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"With this set up we can now cover some best practices."}),"\n",(0,i.jsx)(n.h2,{id:"best-practices",children:"Best practices"}),"\n",(0,i.jsx)(n.p,{children:"Ideally, you should not protect your applications using only guards (Route or Controller guards).\nThis leaves your application open for some undesired side-effects.\nAs a best practice you should protect all your services or controllers by injecting the authorization service.\nBut let's go step by step:"}),"\n",(0,i.jsx)(n.p,{children:"Assuming the application example above we can easily use LmcRbacMvc to protect our route using the following guard:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'post/delete' => ['admin']\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.p,{children:'Now, any users that do not have the "admin" role will receive a 403 error (unauthorized) when trying to access\nthe "post/delete" route. However, this does not prevent the service (which should contain the actual logic in a properly\ndesign application) to be injected and used elsewhere in your code. For instance:'}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function createAction()\n {\n // this action may have been reached through the \"forward\" method, hence bypassing guards\n $this->postService->deletePost('2');\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"You see the issue!"}),"\n",(0,i.jsxs)(n.p,{children:["The solution is to inject the ",(0,i.jsx)(n.code,{children:"AuthorizationService"})," into your services and check for the\npermissions before doing anything wrong. So let's modify our previously created ",(0,i.jsx)(n.code,{children:"PostService"})," class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // First check permission\n if (!$this->authorizationService->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Since we now have an additional dependency we should inject it through our factory, again within our ",(0,i.jsx)(n.code,{children:"Module"})," class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), getConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'PostService' => function($sm) {\n return new PostService(\n $sm->get('doctrine.entitymanager.orm_default'),\n $sm->get('LmcRbacMvc\\Service\\AuthorizationService') // This is new!\n );\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Alternatively, you can also protect your controllers using the ",(0,i.jsx)(n.code,{children:"isGranted"})," helper (you do not need to inject\nthe AuthorizationService then):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function createAction()\n {\n if (!$this->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $this->postService->deletePost('2');\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"While protecting services is the more defensive way (because services are usually the last part of the logic flow),\nit is often complicated to deal with.\nIf your application is architectured correctly, it is often simpler to protect your controllers."}),"\n",(0,i.jsx)(n.h3,{id:"when-using-guards-then",children:"When using guards then?"}),"\n",(0,i.jsx)(n.p,{children:"In fact, you should see guards as a very efficient way to quickly reject access to a hierarchy of routes or a\nwhole controller. For instance, assuming you have the following route config:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'router' => [\n 'routes' => [\n 'admin' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/admin'\n ],\n 'may_terminate' => true,\n 'child_routes' => [\n 'users' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/users'\n ]\n ],\n 'invoices' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/invoices'\n ]\n ]\n ]\n ]\n ]\n ]\n};\n"})}),"\n",(0,i.jsx)(n.p,{children:"You can quickly reject access to all admin routes using the following guard:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin']\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-2---only-delete-your-own-posts",children:"A Real World Application Part 2 - Only delete your own Posts"}),"\n",(0,i.jsxs)(n.p,{children:["If you jumped straight to this section please notice that we assume you have the knowledge that we presented in the\nprevious example. In here we will cover a very common use case. Users of our Application should only have delete\npermissions to their own content. So let's quickly refresh our ",(0,i.jsx)(n.code,{children:"PostService"})," class:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // First check permission\n if (!$this->authorizationService->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["As we can see, we check within our Service if the User of our Application is allowed to delete the post with a check\nagainst the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission. Now how can we achieve that only a user who is the owner of the Post to be able to\ndelete his own post, but other users can't? We do not want to change our Service with more complex logic because this\nis not the task of such service. The Permission-System should handle this. And we can, for this we have the\n",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," and here is how to do it:"]}),"\n",(0,i.jsx)(n.p,{children:"First of all we need to write an Assertion. The Assertion will return a boolean statement about the current\nidentity being the owner of the post."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"namespace Your\\Namespace;\n\nuse LmcRbacMvc\\Assertion\\AssertionInterface;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass MustBeAuthorAssertion implements AssertionInterface\n{\n /**\n * Check if this assertion is true\n *\n * @param AuthorizationService $authorization\n * @param mixed $post\n *\n * @return bool\n */\n public function assert(AuthorizationService $authorization, $post = null)\n {\n return $authorization->getIdentity() === $post->getAuthor();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This simple ",(0,i.jsx)(n.code,{children:"MustBeAuthorAssertion"})," will check against the current ",(0,i.jsx)(n.code,{children:"$authorization"}),' if it equals the identity of the\ncurrent context Author. The second parameter is called the "context". A context can be anything (an object, a scalar,\nan array...) and only makes sense in the context of the assertion.']}),"\n",(0,i.jsxs)(n.p,{children:["Imagine a user calls ",(0,i.jsx)(n.code,{children:"http://my.dom/post/delete/42"}),", so obviously he wants to delete the Post-Entity with ID#42. In\nthis case Entity#42 is our Context! If you're wondering how the context gets there, bare with me. We will get to\nthis later."]}),"\n",(0,i.jsxs)(n.p,{children:["Now that we have written the Assertion, we want to make sure that this assertion will always be called, whenever we\ncheck for the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission. We don't want others to delete our previous content! For this we have the so\ncalled ",(0,i.jsx)(n.code,{children:"assertion_map"}),". In this map we glue ",(0,i.jsx)(n.code,{children:"assertions"})," and ",(0,i.jsx)(n.code,{children:"permissions"})," together."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"// module.config.php or wherever you configure your RBAC stuff\nreturn [\n 'lmc_rbac' => [\n 'assertion_map' => [\n 'deletePost' => 'Your\\Namespace\\MustBeAuthorAssertion'\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now, whenever some test the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission, it will automatically call the ",(0,i.jsx)(n.code,{children:"MustBeAuthorAssertion"})," from\nthe ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"}),". This plugin manager is configured to automatically add unknown classes to an invokable.\nHowever, some assertions may need dependencies. You can manually configure the assertion plugin manager as\nshown below:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"// module.config.php or wherever you configure your RBAC stuff\nreturn [\n 'lmc_rbac' => [\n // ... other rbac stuff\n 'assertion_manager' => [\n 'factories' => [\n 'AssertionWithDependency' => 'Your\\Namespace\\AssertionWithDependencyFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now we need to remember about the ",(0,i.jsx)(n.strong,{children:"context"}),". Somehow we need to let the ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," know about our\ncontext. This is done by simply passing it to the ",(0,i.jsx)(n.code,{children:"isGranted()"})," method. For this we need to modify our Service\none last time."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // Note, we now need to query for the post of interest first!\n $post = $this->objectManager->find('Post', $id);\n\n // Check the permission now with a given context\n if (!$this->authorizationService->isGranted('deletePost', $post)) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["And there you have it. The context is injected into the ",(0,i.jsx)(n.code,{children:"isGranted()"})," method and now the ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," knows\nabout it and can do its thing. Note that in reality, after you have queried for the ",(0,i.jsx)(n.code,{children:"$post"})," you would check if ",(0,i.jsx)(n.code,{children:"$post"}),"\nis actually a real post. Because if it is an empty return value then you should throw an exception earlier without\nneeding to check against the permission."]}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-3---admins-can-delete-everything",children:"A Real World Application Part 3 - Admins can delete everything"}),"\n",(0,i.jsx)(n.p,{children:"Often, you want users with a specific role to be able to have full access to everything. For instance, admins could\ndelete all the posts, even if they don't own it."}),"\n",(0,i.jsxs)(n.p,{children:["However, with the previous assertion, even if the admin has the permission ",(0,i.jsx)(n.code,{children:"deletePost"}),", it won't work because\nthe assertion will evaluate to false."]}),"\n",(0,i.jsxs)(n.p,{children:["Actually, the answer is quite simple: deleting my own posts and deleting others' posts should be treated like\ntwo different permissions (it makes sense if you think about it). Therefore, admins will have the permission\n",(0,i.jsx)(n.code,{children:"deleteOthersPost"})," (as well as the permission ",(0,i.jsx)(n.code,{children:"deletePost"}),", because admin could write posts, too)."]}),"\n",(0,i.jsx)(n.p,{children:"The assertion must therefore be modified like this:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"namespace Your\\Namespace;\n\nuse LmcRbacMvc\\Assertion\\AssertionInterface;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass MustBeAuthorAssertion implements AssertionInterface\n{\n /**\n * Check if this assertion is true\n *\n * @param AuthorizationService $authorization\n * @param mixed $context\n *\n * @return bool\n */\n public function assert(AuthorizationService $authorization, $context = null)\n {\n if ($authorization->getIdentity() === $context->getAuthor()) {\n return true;\n }\n\n return $authorization->isGranted('deleteOthersPost');\n }\n}\n"})}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-4---checking-permissions-in-the-view",children:"A Real World Application Part 4 - Checking permissions in the view"}),"\n",(0,i.jsxs)(n.p,{children:["If some part of the view needs to be protected, you can use the shipped ",(0,i.jsx)(n.code,{children:"isGranted"})," view helper."]}),"\n",(0,i.jsxs)(n.p,{children:["For example, lets's say that only users with the permissions ",(0,i.jsx)(n.code,{children:"post.manage"})," will have a menu item to acces\nthe adminsitration panel :"]}),"\n",(0,i.jsx)(n.p,{children:"In your template post-index.phtml"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'\n'})}),"\n",(0,i.jsxs)(n.p,{children:["You can even protect your menu item regarding a role, by using the ",(0,i.jsx)(n.code,{children:"hasRole"})," view helper :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'\n'})}),"\n",(0,i.jsxs)(n.p,{children:["In this last example, the menu item will be hidden for users who don't have the ",(0,i.jsx)(n.code,{children:"admin"})," role."]}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-with-doctrine-orm",children:"Using LmcRbacMvc with Doctrine ORM"}),"\n",(0,i.jsxs)(n.p,{children:["First your User entity class must implement ",(0,i.jsx)(n.code,{children:"LmcRbacMvc\\Identity\\IdentityInterface"})," :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'use LmccUser\\Entity\\User as LmcUserEntity;\nuse LmcRbacMvc\\Identity\\IdentityInterface;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\n\n/**\n * @ORM\\Entity\n * @ORM\\Table(name="user")\n */\nclass User extends LmcUserEntity implements IdentityInterface\n{\n /**\n * @var Collection\n * @ORM\\ManyToMany(targetEntity="HierarchicalRole")\n */\n private $roles;\n\n public function __construct()\n {\n $this->roles = new ArrayCollection();\n }\n\n /**\n * {@inheritDoc}\n */\n public function getRoles()\n {\n return $this->roles->toArray();\n }\n\n /**\n * Set the list of roles\n * @param Collection $roles\n */\n public function setRoles(Collection $roles)\n {\n $this->roles->clear();\n foreach ($roles as $role) {\n $this->roles[] = $role;\n }\n }\n\n /**\n * Add one role to roles list\n * @param \\Rbac\\Role\\RoleInterface $role\n */\n public function addRole(RoleInterface $role)\n {\n $this->roles[] = $role;\n }\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["For this example we will use a more complex situation by using ",(0,i.jsx)(n.code,{children:"Rbac\\Role\\HierarchicalRoleInterface"})," so the second step is to create HierarchicalRole entity class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'class HierarchicalRole implements HierarchicalRoleInterface\n{\n /**\n * @var HierarchicalRoleInterface[]|\\Doctrine\\Common\\Collections\\Collection\n *\n * @ORM\\ManyToMany(targetEntity="HierarchicalRole")\n */\n protected $children;\n\n /**\n * @var PermissionInterface[]|\\Doctrine\\Common\\Collections\\Collection\n *\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER", cascade={"persist"})\n */\n protected $permissions;\n\n /**\n * Init the Doctrine collection\n */\n public function __construct()\n {\n $this->children = new ArrayCollection();\n $this->permissions = new ArrayCollection();\n }\n\n /**\n * {@inheritDoc}\n */\n public function addChild(HierarchicalRoleInterface $child)\n {\n $this->children[] = $child;\n }\n\n /*\n * Set the list of permission\n * @param Collection $permissions\n */\n public function setPermissions(Collection $permissions)\n {\n $this->permissions->clear();\n foreach ($permissions as $permission) {\n $this->permissions[] = $permission;\n }\n }\n\n /**\n * {@inheritDoc}\n */\n public function addPermission($permission)\n {\n if (is_string($permission)) {\n $permission = new Permission($permission);\n }\n\n $this->permissions[(string) $permission] = $permission;\n }\n\n /**\n * {@inheritDoc}\n */\n public function hasPermission($permission)\n {\n // This can be a performance problem if your role has a lot of permissions. Please refer\n // to the cookbook to an elegant way to solve this issue\n\n return isset($this->permissions[(string) $permission]);\n }\n\n /**\n * {@inheritDoc}\n */\n public function getChildren()\n {\n return $this->children->toArray();\n }\n\n /**\n * {@inheritDoc}\n */\n public function hasChildren()\n {\n return !$this->children->isEmpty();\n }\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:"And the last step is to create a Permission entity class which is a very simple entity class. You don't have to do specific things!"}),"\n",(0,i.jsxs)(n.p,{children:["You can find all entity examples in this folder : ",(0,i.jsx)(n.a,{href:"https://github.com/LM-Commons/LmcRbacMvc/tree/master/data",children:"Example"})]}),"\n",(0,i.jsxs)(n.p,{children:["You need one more configuration step. Indeed, how can the RoleProvider retrieve your role and permissions? For this you need to configure ",(0,i.jsx)(n.code,{children:"LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider"})," in your ",(0,i.jsx)(n.code,{children:"lmc_rbac.global.php"})," file :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:" /**\n * Configuration for role provider\n */\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_manager' => 'doctrine.entitymanager.orm_default', // alias for doctrine ObjectManager\n 'class_name' => 'User\\Entity\\HierarchicalRole', // FQCN for your role entity class\n 'role_name_property' => 'name', // Name to show\n ],\n ],\n"})}),"\n",(0,i.jsx)(n.p,{children:"Using DoctrineORM with LmcRbacMvc is very simple. You need to be aware of performance where there is a lot of permissions for roles."}),"\n",(0,i.jsx)(n.h2,{id:"how-to-deal-with-roles-with-lot-of-permissions",children:"How to deal with roles with lot of permissions?"}),"\n",(0,i.jsxs)(n.p,{children:["In very complex applications, your roles may have dozens of permissions. In the [/data/FlatRole.php.dist] entity\nwe provide, we configure the permissions association so that whenever a role is loaded, all of its permissions are also\nloaded in one query (notice the ",(0,i.jsx)(n.code,{children:'fetch="EAGER"'}),"):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'/**\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER")\n */\nprotected $permissions;\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"hasPermission"})," method is therefore really simple:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"public function hasPermission($permission)\n{\n return isset($this->permissions[(string) $permission]);\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"However, with a lot of permissions, this method will quickly kill your database. What you can do is modfiy the Doctrine\nmapping so that the collection is not actually loaded:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'/**\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="LAZY")\n */\nprotected $permissions;\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Then, modify the ",(0,i.jsx)(n.code,{children:"hasPermission"})," method to use the Criteria API. The Criteria API is a Doctrine 2.2+ API that allows\nyour application to efficiently filter a collection without loading the whole collection:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Common\\Collections\\Criteria;\n\npublic function hasPermission($permission)\n{\n $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) $permission));\n $result = $this->permissions->matching($criteria);\n\n return count($result) > 0;\n}\n"})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"NOTE: This is only supported starting from Doctrine ORM 2.5!"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-and-zf2-assetic",children:"Using LmcRbacMvc and ZF2 Assetic"}),"\n",(0,i.jsxs)(n.p,{children:["To use ",(0,i.jsx)(n.a,{href:"https://github.com/widmogrod/zf2-assetic-module",children:"Assetic"})," with LmcRbacMvc guards, you should modify your\n",(0,i.jsx)(n.code,{children:"module.config.php"})," using the following configuration:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'assetic_configuration' => [\n 'acceptableErrors' => [\n \\LmcRbacMvc\\Guard\\GuardInterface::GUARD_UNAUTHORIZED\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-and-lmcuser",children:"Using LmcRbacMvc and LmcUser"}),"\n",(0,i.jsx)(n.p,{children:"To use the authentication service from LmcUser, just add the following alias in your application.config.php:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'service_manager' => [\n 'aliases' => [\n 'Laminas\\Authentication\\AuthenticationService' => 'lmcuser_auth_service'\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Finally add the LmcUser routes to your ",(0,i.jsx)(n.code,{children:"guards"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbac\\Guard\\RouteGuard' => [\n 'lmcuser/login' => ['guest'],\n 'lmcuser/register' => ['guest'], // required if registration is enabled\n 'lmcuser*' => ['user'] // includes logout, changepassword and changeemail\n ]\n ]\n ]\n];\n"})})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>r,x:()=>c});var i=t(6540);const o={},s=i.createContext(o);function r(e){const n=i.useContext(s);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),i.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b9207088.c1f4a27e.js b/assets/js/b9207088.c1f4a27e.js new file mode 100644 index 00000000..4dd6f216 --- /dev/null +++ b/assets/js/b9207088.c1f4a27e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[571],{8849:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>s,metadata:()=>c,toc:()=>l});var i=t(4848),o=t(8453);const s={sidebar_position:8},r="Cookbook",c={id:"cookbook",title:"Cookbook",description:"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have",source:"@site/docs/cookbook.md",sourceDirName:".",slug:"/cookbook",permalink:"/lmc-rbac-mvc/docs/cookbook",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/cookbook.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{sidebar_position:8},sidebar:"tutorialSidebar",previous:{title:"Using the Authorization Service",permalink:"/lmc-rbac-mvc/docs/using-the-authorization-service"},next:{title:"Support",permalink:"/lmc-rbac-mvc/docs/support"}},a={},l=[{value:"A Real World Application",id:"a-real-world-application",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"When using guards then?",id:"when-using-guards-then",level:3},{value:"A Real World Application Part 2 - Only delete your own Posts",id:"a-real-world-application-part-2---only-delete-your-own-posts",level:2},{value:"A Real World Application Part 3 - Admins can delete everything",id:"a-real-world-application-part-3---admins-can-delete-everything",level:2},{value:"A Real World Application Part 4 - Checking permissions in the view",id:"a-real-world-application-part-4---checking-permissions-in-the-view",level:2},{value:"Using LmcRbacMvc with Doctrine ORM",id:"using-lmcrbacmvc-with-doctrine-orm",level:2},{value:"How to deal with roles with lot of permissions?",id:"how-to-deal-with-roles-with-lot-of-permissions",level:2},{value:"Using LmcRbacMvc and ZF2 Assetic",id:"using-lmcrbacmvc-and-zf2-assetic",level:2},{value:"Using LmcRbacMvc and LmcUser",id:"using-lmcrbacmvc-and-lmcuser",level:2}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",strong:"strong",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h1,{id:"cookbook",children:"Cookbook"}),"\n",(0,i.jsx)(n.p,{children:"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have\nany other recipe you'd like to add, please open an issue!"}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application",children:"A Real World Application"}),"\n",(0,i.jsxs)(n.p,{children:["In this example we are going to create a very little real world application. We will create a controller\n",(0,i.jsx)(n.code,{children:"PostController"})," that interacts with a service called ",(0,i.jsx)(n.code,{children:"PostService"}),". For the sake of simplicity we will only\ncover the ",(0,i.jsx)(n.code,{children:"delete"}),"-methods of both parts."]}),"\n",(0,i.jsxs)(n.p,{children:["Let's start by creating a controller that has the ",(0,i.jsx)(n.code,{children:"PostService"})," as dependency:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function __construct(PostService $postService)\n {\n $this->postService = $postService;\n }\n\n // addAction(), editAction(), etc...\n\n public function deleteAction()\n {\n $id = $this->params()->fromQuery('id');\n\n $this->postService->deletePost($id);\n\n return $this->redirect()->toRoute('posts');\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Since we have a dependency, let's inject it using the ",(0,i.jsx)(n.code,{children:"ControllerManager"}),", we will do this inside our ",(0,i.jsx)(n.code,{children:"Module"})," class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n public function getConfig()\n {\n return [\n 'controllers' => [\n 'factories' => [\n 'PostController' => function ($cpm) {\n // We assume a Service key 'PostService' here that returns the PostService Class\n return new PostController(\n $cpm->getServiceLocator()->get('PostService')\n );\n },\n ],\n ],\n ];\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now that we have this in place let us quickly define our ",(0,i.jsx)(n.code,{children:"PostService"}),". We will be using a Service that makes use\nof Doctrine, so we require a ",(0,i.jsx)(n.code,{children:"Doctrine\\Persistence\\ObjectManager"})," as dependency."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n public function __construct(ObjectManager $objectManager)\n {\n $this->objectManager = $objectManager;\n }\n\n public function deletePost($id)\n {\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["And for this one, too, let's quickly create the factory, again within our ",(0,i.jsx)(n.code,{children:"Module"})," class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), getConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'PostService' => function($sm) {\n return new PostService(\n $sm->get('doctrine.entitymanager.orm_default')\n );\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"With this set up we can now cover some best practices."}),"\n",(0,i.jsx)(n.h2,{id:"best-practices",children:"Best practices"}),"\n",(0,i.jsx)(n.p,{children:"Ideally, you should not protect your applications using only guards (Route or Controller guards).\nThis leaves your application open for some undesired side-effects.\nAs a best practice you should protect all your services or controllers by injecting the authorization service.\nBut let's go step by step:"}),"\n",(0,i.jsx)(n.p,{children:"Assuming the application example above we can easily use LmcRbacMvc to protect our route using the following guard:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'post/delete' => ['admin']\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.p,{children:'Now, any users that do not have the "admin" role will receive a 403 error (unauthorized) when trying to access\nthe "post/delete" route. However, this does not prevent the service (which should contain the actual logic in a properly\ndesign application) to be injected and used elsewhere in your code. For instance:'}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function createAction()\n {\n // this action may have been reached through the \"forward\" method, hence bypassing guards\n $this->postService->deletePost('2');\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"You see the issue!"}),"\n",(0,i.jsxs)(n.p,{children:["The solution is to inject the ",(0,i.jsx)(n.code,{children:"AuthorizationService"})," into your services and check for the\npermissions before doing anything wrong. So let's modify our previously created ",(0,i.jsx)(n.code,{children:"PostService"})," class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // First check permission\n if (!$this->authorizationService->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Since we now have an additional dependency we should inject it through our factory, again within our ",(0,i.jsx)(n.code,{children:"Module"})," class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), getConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'PostService' => function($sm) {\n return new PostService(\n $sm->get('doctrine.entitymanager.orm_default'),\n $sm->get('LmcRbacMvc\\Service\\AuthorizationService') // This is new!\n );\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Alternatively, you can also protect your controllers using the ",(0,i.jsx)(n.code,{children:"isGranted"})," helper (you do not need to inject\nthe AuthorizationService then):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function createAction()\n {\n if (!$this->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $this->postService->deletePost('2');\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"While protecting services is the more defensive way (because services are usually the last part of the logic flow),\nit is often complicated to deal with.\nIf your application is architectured correctly, it is often simpler to protect your controllers."}),"\n",(0,i.jsx)(n.h3,{id:"when-using-guards-then",children:"When using guards then?"}),"\n",(0,i.jsx)(n.p,{children:"In fact, you should see guards as a very efficient way to quickly reject access to a hierarchy of routes or a\nwhole controller. For instance, assuming you have the following route config:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'router' => [\n 'routes' => [\n 'admin' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/admin'\n ],\n 'may_terminate' => true,\n 'child_routes' => [\n 'users' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/users'\n ]\n ],\n 'invoices' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/invoices'\n ]\n ]\n ]\n ]\n ]\n ]\n};\n"})}),"\n",(0,i.jsx)(n.p,{children:"You can quickly reject access to all admin routes using the following guard:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin']\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-2---only-delete-your-own-posts",children:"A Real World Application Part 2 - Only delete your own Posts"}),"\n",(0,i.jsxs)(n.p,{children:["If you jumped straight to this section please notice that we assume you have the knowledge that we presented in the\nprevious example. In here we will cover a very common use case. Users of our Application should only have delete\npermissions to their own content. So let's quickly refresh our ",(0,i.jsx)(n.code,{children:"PostService"})," class:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // First check permission\n if (!$this->authorizationService->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["As we can see, we check within our Service if the User of our Application is allowed to delete the post with a check\nagainst the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission. Now how can we achieve that only a user who is the owner of the Post to be able to\ndelete his own post, but other users can't? We do not want to change our Service with more complex logic because this\nis not the task of such service. The Permission-System should handle this. And we can, for this we have the\n",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," and here is how to do it:"]}),"\n",(0,i.jsx)(n.p,{children:"First of all we need to write an Assertion. The Assertion will return a boolean statement about the current\nidentity being the owner of the post."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"namespace Your\\Namespace;\n\nuse LmcRbacMvc\\Assertion\\AssertionInterface;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass MustBeAuthorAssertion implements AssertionInterface\n{\n /**\n * Check if this assertion is true\n *\n * @param AuthorizationService $authorization\n * @param mixed $post\n *\n * @return bool\n */\n public function assert(AuthorizationService $authorization, $post = null)\n {\n return $authorization->getIdentity() === $post->getAuthor();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This simple ",(0,i.jsx)(n.code,{children:"MustBeAuthorAssertion"})," will check against the current ",(0,i.jsx)(n.code,{children:"$authorization"}),' if it equals the identity of the\ncurrent context Author. The second parameter is called the "context". A context can be anything (an object, a scalar,\nan array...) and only makes sense in the context of the assertion.']}),"\n",(0,i.jsxs)(n.p,{children:["Imagine a user calls ",(0,i.jsx)(n.code,{children:"http://my.dom/post/delete/42"}),", so obviously he wants to delete the Post-Entity with ID#42. In\nthis case Entity#42 is our Context! If you're wondering how the context gets there, bare with me. We will get to\nthis later."]}),"\n",(0,i.jsxs)(n.p,{children:["Now that we have written the Assertion, we want to make sure that this assertion will always be called, whenever we\ncheck for the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission. We don't want others to delete our previous content! For this we have the so\ncalled ",(0,i.jsx)(n.code,{children:"assertion_map"}),". In this map we glue ",(0,i.jsx)(n.code,{children:"assertions"})," and ",(0,i.jsx)(n.code,{children:"permissions"})," together."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"// module.config.php or wherever you configure your RBAC stuff\nreturn [\n 'lmc_rbac' => [\n 'assertion_map' => [\n 'deletePost' => 'Your\\Namespace\\MustBeAuthorAssertion'\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now, whenever some test the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission, it will automatically call the ",(0,i.jsx)(n.code,{children:"MustBeAuthorAssertion"})," from\nthe ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"}),". This plugin manager is configured to automatically add unknown classes to an invokable.\nHowever, some assertions may need dependencies. You can manually configure the assertion plugin manager as\nshown below:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"// module.config.php or wherever you configure your RBAC stuff\nreturn [\n 'lmc_rbac' => [\n // ... other rbac stuff\n 'assertion_manager' => [\n 'factories' => [\n 'AssertionWithDependency' => 'Your\\Namespace\\AssertionWithDependencyFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now we need to remember about the ",(0,i.jsx)(n.strong,{children:"context"}),". Somehow we need to let the ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," know about our\ncontext. This is done by simply passing it to the ",(0,i.jsx)(n.code,{children:"isGranted()"})," method. For this we need to modify our Service\none last time."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // Note, we now need to query for the post of interest first!\n $post = $this->objectManager->find('Post', $id);\n\n // Check the permission now with a given context\n if (!$this->authorizationService->isGranted('deletePost', $post)) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["And there you have it. The context is injected into the ",(0,i.jsx)(n.code,{children:"isGranted()"})," method and now the ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," knows\nabout it and can do its thing. Note that in reality, after you have queried for the ",(0,i.jsx)(n.code,{children:"$post"})," you would check if ",(0,i.jsx)(n.code,{children:"$post"}),"\nis actually a real post. Because if it is an empty return value then you should throw an exception earlier without\nneeding to check against the permission."]}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-3---admins-can-delete-everything",children:"A Real World Application Part 3 - Admins can delete everything"}),"\n",(0,i.jsx)(n.p,{children:"Often, you want users with a specific role to be able to have full access to everything. For instance, admins could\ndelete all the posts, even if they don't own it."}),"\n",(0,i.jsxs)(n.p,{children:["However, with the previous assertion, even if the admin has the permission ",(0,i.jsx)(n.code,{children:"deletePost"}),", it won't work because\nthe assertion will evaluate to false."]}),"\n",(0,i.jsxs)(n.p,{children:["Actually, the answer is quite simple: deleting my own posts and deleting others' posts should be treated like\ntwo different permissions (it makes sense if you think about it). Therefore, admins will have the permission\n",(0,i.jsx)(n.code,{children:"deleteOthersPost"})," (as well as the permission ",(0,i.jsx)(n.code,{children:"deletePost"}),", because admin could write posts, too)."]}),"\n",(0,i.jsx)(n.p,{children:"The assertion must therefore be modified like this:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"namespace Your\\Namespace;\n\nuse LmcRbacMvc\\Assertion\\AssertionInterface;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass MustBeAuthorAssertion implements AssertionInterface\n{\n /**\n * Check if this assertion is true\n *\n * @param AuthorizationService $authorization\n * @param mixed $context\n *\n * @return bool\n */\n public function assert(AuthorizationService $authorization, $context = null)\n {\n if ($authorization->getIdentity() === $context->getAuthor()) {\n return true;\n }\n\n return $authorization->isGranted('deleteOthersPost');\n }\n}\n"})}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-4---checking-permissions-in-the-view",children:"A Real World Application Part 4 - Checking permissions in the view"}),"\n",(0,i.jsxs)(n.p,{children:["If some part of the view needs to be protected, you can use the shipped ",(0,i.jsx)(n.code,{children:"isGranted"})," view helper."]}),"\n",(0,i.jsxs)(n.p,{children:["For example, lets's say that only users with the permissions ",(0,i.jsx)(n.code,{children:"post.manage"})," will have a menu item to acces\nthe adminsitration panel :"]}),"\n",(0,i.jsx)(n.p,{children:"In your template post-index.phtml"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'\n'})}),"\n",(0,i.jsxs)(n.p,{children:["You can even protect your menu item regarding a role, by using the ",(0,i.jsx)(n.code,{children:"hasRole"})," view helper :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'\n'})}),"\n",(0,i.jsxs)(n.p,{children:["In this last example, the menu item will be hidden for users who don't have the ",(0,i.jsx)(n.code,{children:"admin"})," role."]}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-with-doctrine-orm",children:"Using LmcRbacMvc with Doctrine ORM"}),"\n",(0,i.jsxs)(n.p,{children:["First your User entity class must implement ",(0,i.jsx)(n.code,{children:"LmcRbacMvc\\Identity\\IdentityInterface"})," :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'use LmccUser\\Entity\\User as LmcUserEntity;\nuse LmcRbacMvc\\Identity\\IdentityInterface;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\n\n/**\n * @ORM\\Entity\n * @ORM\\Table(name="user")\n */\nclass User extends LmcUserEntity implements IdentityInterface\n{\n /**\n * @var Collection\n * @ORM\\ManyToMany(targetEntity="HierarchicalRole")\n */\n private $roles;\n\n public function __construct()\n {\n $this->roles = new ArrayCollection();\n }\n\n /**\n * {@inheritDoc}\n */\n public function getRoles()\n {\n return $this->roles->toArray();\n }\n\n /**\n * Set the list of roles\n * @param Collection $roles\n */\n public function setRoles(Collection $roles)\n {\n $this->roles->clear();\n foreach ($roles as $role) {\n $this->roles[] = $role;\n }\n }\n\n /**\n * Add one role to roles list\n * @param \\Rbac\\Role\\RoleInterface $role\n */\n public function addRole(RoleInterface $role)\n {\n $this->roles[] = $role;\n }\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["For this example we will use a more complex situation by using ",(0,i.jsx)(n.code,{children:"Rbac\\Role\\HierarchicalRoleInterface"})," so the second step is to create HierarchicalRole entity class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'class HierarchicalRole implements HierarchicalRoleInterface\n{\n /**\n * @var HierarchicalRoleInterface[]|\\Doctrine\\Common\\Collections\\Collection\n *\n * @ORM\\ManyToMany(targetEntity="HierarchicalRole")\n */\n protected $children;\n\n /**\n * @var PermissionInterface[]|\\Doctrine\\Common\\Collections\\Collection\n *\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER", cascade={"persist"})\n */\n protected $permissions;\n\n /**\n * Init the Doctrine collection\n */\n public function __construct()\n {\n $this->children = new ArrayCollection();\n $this->permissions = new ArrayCollection();\n }\n\n /**\n * {@inheritDoc}\n */\n public function addChild(HierarchicalRoleInterface $child)\n {\n $this->children[] = $child;\n }\n\n /*\n * Set the list of permission\n * @param Collection $permissions\n */\n public function setPermissions(Collection $permissions)\n {\n $this->permissions->clear();\n foreach ($permissions as $permission) {\n $this->permissions[] = $permission;\n }\n }\n\n /**\n * {@inheritDoc}\n */\n public function addPermission($permission)\n {\n if (is_string($permission)) {\n $permission = new Permission($permission);\n }\n\n $this->permissions[(string) $permission] = $permission;\n }\n\n /**\n * {@inheritDoc}\n */\n public function hasPermission($permission)\n {\n // This can be a performance problem if your role has a lot of permissions. Please refer\n // to the cookbook to an elegant way to solve this issue\n\n return isset($this->permissions[(string) $permission]);\n }\n\n /**\n * {@inheritDoc}\n */\n public function getChildren()\n {\n return $this->children->toArray();\n }\n\n /**\n * {@inheritDoc}\n */\n public function hasChildren()\n {\n return !$this->children->isEmpty();\n }\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:"And the last step is to create a Permission entity class which is a very simple entity class. You don't have to do specific things!"}),"\n",(0,i.jsxs)(n.p,{children:["You can find all entity examples in this folder : ",(0,i.jsx)(n.a,{href:"https://github.com/LM-Commons/LmcRbacMvc/tree/master/data",children:"Example"})]}),"\n",(0,i.jsxs)(n.p,{children:["You need one more configuration step. Indeed, how can the RoleProvider retrieve your role and permissions? For this you need to configure ",(0,i.jsx)(n.code,{children:"LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider"})," in your ",(0,i.jsx)(n.code,{children:"lmc_rbac.global.php"})," file :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:" /**\n * Configuration for role provider\n */\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_manager' => 'doctrine.entitymanager.orm_default', // alias for doctrine ObjectManager\n 'class_name' => 'User\\Entity\\HierarchicalRole', // FQCN for your role entity class\n 'role_name_property' => 'name', // Name to show\n ],\n ],\n"})}),"\n",(0,i.jsx)(n.p,{children:"Using DoctrineORM with LmcRbacMvc is very simple. You need to be aware of performance where there is a lot of permissions for roles."}),"\n",(0,i.jsx)(n.h2,{id:"how-to-deal-with-roles-with-lot-of-permissions",children:"How to deal with roles with lot of permissions?"}),"\n",(0,i.jsxs)(n.p,{children:["In very complex applications, your roles may have dozens of permissions. In the [/data/FlatRole.php.dist] entity\nwe provide, we configure the permissions association so that whenever a role is loaded, all of its permissions are also\nloaded in one query (notice the ",(0,i.jsx)(n.code,{children:'fetch="EAGER"'}),"):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'/**\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER")\n */\nprotected $permissions;\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"hasPermission"})," method is therefore really simple:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"public function hasPermission($permission)\n{\n return isset($this->permissions[(string) $permission]);\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"However, with a lot of permissions, this method will quickly kill your database. What you can do is modfiy the Doctrine\nmapping so that the collection is not actually loaded:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'/**\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="LAZY")\n */\nprotected $permissions;\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Then, modify the ",(0,i.jsx)(n.code,{children:"hasPermission"})," method to use the Criteria API. The Criteria API is a Doctrine 2.2+ API that allows\nyour application to efficiently filter a collection without loading the whole collection:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Common\\Collections\\Criteria;\n\npublic function hasPermission($permission)\n{\n $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) $permission));\n $result = $this->permissions->matching($criteria);\n\n return count($result) > 0;\n}\n"})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"NOTE: This is only supported starting from Doctrine ORM 2.5!"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-and-zf2-assetic",children:"Using LmcRbacMvc and ZF2 Assetic"}),"\n",(0,i.jsxs)(n.p,{children:["To use ",(0,i.jsx)(n.a,{href:"https://github.com/widmogrod/zf2-assetic-module",children:"Assetic"})," with LmcRbacMvc guards, you should modify your\n",(0,i.jsx)(n.code,{children:"module.config.php"})," using the following configuration:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'assetic_configuration' => [\n 'acceptableErrors' => [\n \\LmcRbacMvc\\Guard\\GuardInterface::GUARD_UNAUTHORIZED\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-and-lmcuser",children:"Using LmcRbacMvc and LmcUser"}),"\n",(0,i.jsx)(n.p,{children:"To use the authentication service from LmcUser, just add the following alias in your application.config.php:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'service_manager' => [\n 'aliases' => [\n 'Laminas\\Authentication\\AuthenticationService' => 'lmcuser_auth_service'\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Finally add the LmcUser routes to your ",(0,i.jsx)(n.code,{children:"guards"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbac\\Guard\\RouteGuard' => [\n 'lmcuser/login' => ['guest'],\n 'lmcuser/register' => ['guest'], // required if registration is enabled\n 'lmcuser*' => ['user'] // includes logout, changepassword and changeemail\n ]\n ]\n ]\n];\n"})})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>r,x:()=>c});var i=t(6540);const o={},s=i.createContext(o);function r(e){const n=i.useContext(s);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),i.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c0482cd2.0f46bdb5.js b/assets/js/c0482cd2.0f46bdb5.js deleted file mode 100644 index cd7ff292..00000000 --- a/assets/js/c0482cd2.0f46bdb5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[133],{3228:e=>{e.exports=JSON.parse('{"permalink":"/lmcrbacmvc/blog/tags/php","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/d9e16301.5f01f94d.js b/assets/js/d9e16301.5f01f94d.js new file mode 100644 index 00000000..7eba4d63 --- /dev/null +++ b/assets/js/d9e16301.5f01f94d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[416],{5989:(s,t,e)=>{e.r(t),e.d(t,{assets:()=>a,contentTitle:()=>c,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>m});var o=e(4848),n=e(8453);const i={sidebar_position:20,title:"Support"},c=void 0,r={id:"support",title:"Support",description:"- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues.",source:"@site/docs/support.md",sourceDirName:".",slug:"/support",permalink:"/lmc-rbac-mvc/docs/support",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/support.md",tags:[],version:"current",sidebarPosition:20,frontMatter:{sidebar_position:20,title:"Support"},sidebar:"tutorialSidebar",previous:{title:"Cookbook",permalink:"/lmc-rbac-mvc/docs/cookbook"}},a={},m=[{value:"Notices and Disclaimers",id:"notices-and-disclaimers",level:5}];function d(s){const t={a:"a",h5:"h5",li:"li",p:"p",ul:"ul",...(0,n.R)(),...s.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:["File issues at ",(0,o.jsx)(t.a,{href:"https://github.com/LM-Commons/LmcRbacMvc/issues",children:"https://github.com/LM-Commons/LmcRbacMvc/issues"}),"."]}),"\n",(0,o.jsxs)(t.li,{children:["Ask questions in the ",(0,o.jsx)(t.a,{href:"https://gitter.im/LM-Commons/community",children:"LM-Commons Gitter"})," chat."]}),"\n"]}),"\n",(0,o.jsx)(t.h5,{id:"notices-and-disclaimers",children:"Notices and Disclaimers"}),"\n",(0,o.jsx)(t.p,{children:"This is not an official Laminas Project organization."}),"\n",(0,o.jsx)(t.p,{children:"Issues and questions related to the Laminas MVC and components\nshould be addressed to the Laminas Project organisation."}),"\n",(0,o.jsx)(t.p,{children:"Laminas is a trademark of the Laminas Project, a Series of LF Projects, LLC."})]})}function l(s={}){const{wrapper:t}={...(0,n.R)(),...s.components};return t?(0,o.jsx)(t,{...s,children:(0,o.jsx)(d,{...s})}):d(s)}},8453:(s,t,e)=>{e.d(t,{R:()=>c,x:()=>r});var o=e(6540);const n={},i=o.createContext(n);function c(s){const t=o.useContext(i);return o.useMemo((function(){return"function"==typeof s?s(t):{...t,...s}}),[t,s])}function r(s){let t;return t=s.disableParentContext?"function"==typeof s.components?s.components(n):s.components||n:c(s.components),o.createElement(i.Provider,{value:t},s.children)}}}]); \ No newline at end of file diff --git a/assets/js/d9e16301.eedbd937.js b/assets/js/d9e16301.eedbd937.js deleted file mode 100644 index bb76b97d..00000000 --- a/assets/js/d9e16301.eedbd937.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[416],{5989:(s,t,e)=>{e.r(t),e.d(t,{assets:()=>a,contentTitle:()=>c,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>m});var o=e(4848),n=e(8453);const i={sidebar_position:20,title:"Support"},c=void 0,r={id:"support",title:"Support",description:"- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues.",source:"@site/docs/support.md",sourceDirName:".",slug:"/support",permalink:"/lmcrbacmvc/docs/support",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/support.md",tags:[],version:"current",sidebarPosition:20,frontMatter:{sidebar_position:20,title:"Support"},sidebar:"tutorialSidebar",previous:{title:"Cookbook",permalink:"/lmcrbacmvc/docs/cookbook"}},a={},m=[{value:"Notices and Disclaimers",id:"notices-and-disclaimers",level:5}];function d(s){const t={a:"a",h5:"h5",li:"li",p:"p",ul:"ul",...(0,n.R)(),...s.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:["File issues at ",(0,o.jsx)(t.a,{href:"https://github.com/LM-Commons/LmcRbacMvc/issues",children:"https://github.com/LM-Commons/LmcRbacMvc/issues"}),"."]}),"\n",(0,o.jsxs)(t.li,{children:["Ask questions in the ",(0,o.jsx)(t.a,{href:"https://gitter.im/LM-Commons/community",children:"LM-Commons Gitter"})," chat."]}),"\n"]}),"\n",(0,o.jsx)(t.h5,{id:"notices-and-disclaimers",children:"Notices and Disclaimers"}),"\n",(0,o.jsx)(t.p,{children:"This is not an official Laminas Project organization."}),"\n",(0,o.jsx)(t.p,{children:"Issues and questions related to the Laminas MVC and components\nshould be addressed to the Laminas Project organisation."}),"\n",(0,o.jsx)(t.p,{children:"Laminas is a trademark of the Laminas Project, a Series of LF Projects, LLC."})]})}function l(s={}){const{wrapper:t}={...(0,n.R)(),...s.components};return t?(0,o.jsx)(t,{...s,children:(0,o.jsx)(d,{...s})}):d(s)}},8453:(s,t,e)=>{e.d(t,{R:()=>c,x:()=>r});var o=e(6540);const n={},i=o.createContext(n);function c(s){const t=o.useContext(i);return o.useMemo((function(){return"function"==typeof s?s(t):{...t,...s}}),[t,s])}function r(s){let t;return t=s.disableParentContext?"function"==typeof s.components?s.components(n):s.components||n:c(s.components),o.createElement(i.Provider,{value:t},s.children)}}}]); \ No newline at end of file diff --git a/assets/js/ee696d42.cd7325d3.js b/assets/js/ee696d42.cd7325d3.js new file mode 100644 index 00000000..df893918 --- /dev/null +++ b/assets/js/ee696d42.cd7325d3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[18],{3531:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>m});var n=o(4848),c=o(8453);const r={slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},s=void 0,a={permalink:"/lmc-rbac-mvc/blog/welcome",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md",source:"@site/blog/2022-08-02-welcome.md",title:"Welcome",description:"Welcome to the new documentation website for the LM-Commons organization.",date:"2022-08-02T00:00:00.000Z",formattedDate:"August 2, 2022",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"}],readingTime:.155,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},unlisted:!1,prevItem:{title:"New documentation",permalink:"/lmc-rbac-mvc/blog/new-documentation"}},i={authorsImageUrls:[void 0]},m=[];function l(e){const t={p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.p,{children:"Welcome to the new documentation website for the LM-Commons organization."}),"\n",(0,n.jsx)(t.p,{children:"This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."})]})}function u(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>s,x:()=>a});var n=o(6540);const c={},r=n.createContext(c);function s(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/ee696d42.f815cfcc.js b/assets/js/ee696d42.f815cfcc.js deleted file mode 100644 index 00a988b1..00000000 --- a/assets/js/ee696d42.f815cfcc.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[18],{3531:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>m});var n=o(4848),c=o(8453);const r={slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},s=void 0,a={permalink:"/lmcrbacmvc/blog/welcome",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md",source:"@site/blog/2022-08-02-welcome.md",title:"Welcome",description:"Welcome to the new documentation website for the LM-Commons organization.",date:"2022-08-02T00:00:00.000Z",formattedDate:"August 2, 2022",tags:[{label:"laminas",permalink:"/lmcrbacmvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmcrbacmvc/blog/tags/php"}],readingTime:.155,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},unlisted:!1,prevItem:{title:"New documentation",permalink:"/lmcrbacmvc/blog/new-documentation"}},i={authorsImageUrls:[void 0]},m=[];function l(e){const t={p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.p,{children:"Welcome to the new documentation website for the LM-Commons organization."}),"\n",(0,n.jsx)(t.p,{children:"This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."})]})}function u(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>s,x:()=>a});var n=o(6540);const c={},r=n.createContext(c);function s(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f01051f8.63b05f50.js b/assets/js/f01051f8.63b05f50.js new file mode 100644 index 00000000..905ea9a3 --- /dev/null +++ b/assets/js/f01051f8.63b05f50.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[822],{5409:s=>{s.exports=JSON.parse('{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php","allTagsPath":"/lmc-rbac-mvc/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/f6027d55.272e2251.js b/assets/js/f6027d55.272e2251.js new file mode 100644 index 00000000..729fa924 --- /dev/null +++ b/assets/js/f6027d55.272e2251.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[246],{9477:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>a,default:()=>p,frontMatter:()=>i,metadata:()=>c,toc:()=>l});var r=n(4848),s=n(8453);const i={sidebar_position:6},a="Strategies",c={id:"strategies",title:"Strategies",description:"In this section, you will learn:",source:"@site/docs/strategies.md",sourceDirName:".",slug:"/strategies",permalink:"/lmc-rbac-mvc/docs/strategies",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/strategies.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{sidebar_position:6},sidebar:"tutorialSidebar",previous:{title:"Guards",permalink:"/lmc-rbac-mvc/docs/guards"},next:{title:"Using the Authorization Service",permalink:"/lmc-rbac-mvc/docs/using-the-authorization-service"}},o={},l=[{value:"What are strategies?",id:"what-are-strategies",level:2},{value:"Built-in strategies",id:"built-in-strategies",level:2},{value:"RedirectStrategy",id:"redirectstrategy",level:3},{value:"UnauthorizedStrategy",id:"unauthorizedstrategy",level:3},{value:"Creating custom strategies",id:"creating-custom-strategies",level:2}];function d(e){const t={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h1,{id:"strategies",children:"Strategies"}),"\n",(0,r.jsx)(t.p,{children:"In this section, you will learn:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"What strategies are"}),"\n",(0,r.jsx)(t.li,{children:"How to use built-in strategies"}),"\n",(0,r.jsx)(t.li,{children:"How to create custom strategies"}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"what-are-strategies",children:"What are strategies?"}),"\n",(0,r.jsxs)(t.p,{children:["A strategy is an object that listens to the ",(0,r.jsx)(t.code,{children:"MvcEvent::EVENT_DISPATCH_ERROR"})," event. It is used to describe what\nhappens when access to a resource is unauthorized by LmcRbacMvc."]}),"\n",(0,r.jsxs)(t.p,{children:["LmcRbacMvc strategies all check if an ",(0,r.jsx)(t.code,{children:"LmcRbacMvc\\Exception\\UnauthorizedExceptionInterface"})," has been thrown."]}),"\n",(0,r.jsxs)(t.p,{children:["By default, LmcRbacMvc does not register any strategy for you. The best place to register it is in your ",(0,r.jsx)(t.code,{children:"onBootstrap"}),"\nmethod of the ",(0,r.jsx)(t.code,{children:"Module.php"})," class:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\UnauthorizedStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsx)(t.h2,{id:"built-in-strategies",children:"Built-in strategies"}),"\n",(0,r.jsxs)(t.p,{children:["LmcRbacMvc comes with two built-in strategies: ",(0,r.jsx)(t.code,{children:"RedirectStrategy"})," and ",(0,r.jsx)(t.code,{children:"UnauthorizedStrategy"}),"."]}),"\n",(0,r.jsx)(t.h3,{id:"redirectstrategy",children:"RedirectStrategy"}),"\n",(0,r.jsx)(t.p,{children:"This strategy allows your application to redirect any unauthorized request to another route by optionally appending the previous\nURL as a query parameter."}),"\n",(0,r.jsx)(t.p,{children:"To register it, copy-paste this code into your Module.php class:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\RedirectStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["You can configure the strategy using the ",(0,r.jsx)(t.code,{children:"redirect_strategy"})," subkey:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'redirect_strategy' => [\n 'redirect_when_connected' => true,\n 'redirect_to_route_connected' => 'home',\n 'redirect_to_route_disconnected' => 'login',\n 'append_previous_uri' => true,\n 'previous_uri_query_key' => 'redirectTo'\n ],\n ]\n];\n"})}),"\n",(0,r.jsxs)(t.p,{children:["If users try to access an unauthorized resource (eg.: ",(0,r.jsx)(t.a,{href:"http://www.example.com/delete",children:"http://www.example.com/delete"}),'), they will be\nredirected to the "login" route if is not connected and to the "home" route otherwise (it must exist in your route configuration\nof course) with the previous URL appended : ',(0,r.jsx)(t.a,{href:"http://www.example.com/login?redirectTo=http://www.example.com/delete",children:"http://www.example.com/login?redirectTo=http://www.example.com/delete"})]}),"\n",(0,r.jsxs)(t.p,{children:["You can prevent redirection when a user is connected (i.e. so that the user gets a 403 page) by setting ",(0,r.jsx)(t.code,{children:"redirect_when_connected"})," to ",(0,r.jsx)(t.code,{children:"false"}),"."]}),"\n",(0,r.jsx)(t.h3,{id:"unauthorizedstrategy",children:"UnauthorizedStrategy"}),"\n",(0,r.jsx)(t.p,{children:"This strategy allows your application to render a template on any unauthorized request."}),"\n",(0,r.jsx)(t.p,{children:"To register it, copy-paste this code into your Module.php class:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\UnauthorizedStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["You can configure the strategy using the ",(0,r.jsx)(t.code,{children:"unauthorized_strategy"})," subkey:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'unauthorized_strategy' => [\n 'template' => 'error/custom-403'\n ],\n ]\n];\n"})}),"\n",(0,r.jsxs)(t.blockquote,{children:["\n",(0,r.jsxs)(t.p,{children:["By default, LmcRbacMvc uses a template called ",(0,r.jsx)(t.code,{children:"error/403"}),"."]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"creating-custom-strategies",children:"Creating custom strategies"}),"\n",(0,r.jsxs)(t.p,{children:["Creating a custom strategy is rather easy. Let's say we want to create a strategy that integrates with\nthe ",(0,r.jsx)(t.a,{href:"https://github.com/laminas-api-tools/api-tools-api-problem",children:"ApiProblem"})," Laminas Api Tools module:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"namespace Application\\View\\Strategy;\n\nuse Laminas\\Http\\Response as HttpResponse;\nuse Laminas\\Mvc\\MvcEvent;\nuse Laminas\\ApiTools\\ApiProblem\\ApiProblem;\nuse Laminas\\ApiTools\\ApiProblem\\ApiProblemResponse;\nuse LmcRbacMvc\\View\\Strategy\\AbstractStrategy;\nuse LmcRbacMvc\\Exception\\UnauthorizedExceptionInterface;\n\nclass ApiProblemStrategy extends AbstractStrategy\n{\n public function onError(MvcEvent $event)\n {\n // Do nothing if no error or if response is not HTTP response\n if (!($exception = $event->getParam('exception') instanceof UnauthorizedExceptionInterface)\n || ($result = $event->getResult() instanceof HttpResponse)\n || !($response = $event->getResponse() instanceof HttpResponse)\n ) {\n return;\n }\n\n return new ApiProblemResponse(new ApiProblem($exception->getMessage()));\n }\n}\n"})}),"\n",(0,r.jsx)(t.p,{children:"Register your strategy:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(EventInterface $e)\n{\n $e->getTarget()\n ->getEventManager()\n ->attach(new ApiProblemStrategy());\n}\n"})})]})}function p(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>c});var r=n(6540);const s={},i=r.createContext(s);function a(e){const t=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f6027d55.54c0669c.js b/assets/js/f6027d55.54c0669c.js deleted file mode 100644 index 2ac4ef74..00000000 --- a/assets/js/f6027d55.54c0669c.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[246],{9477:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>a,default:()=>p,frontMatter:()=>i,metadata:()=>c,toc:()=>l});var r=n(4848),s=n(8453);const i={sidebar_position:6},a="Strategies",c={id:"strategies",title:"Strategies",description:"In this section, you will learn:",source:"@site/docs/strategies.md",sourceDirName:".",slug:"/strategies",permalink:"/lmcrbacmvc/docs/strategies",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/strategies.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{sidebar_position:6},sidebar:"tutorialSidebar",previous:{title:"Guards",permalink:"/lmcrbacmvc/docs/guards"},next:{title:"Using the Authorization Service",permalink:"/lmcrbacmvc/docs/using-the-authorization-service"}},o={},l=[{value:"What are strategies?",id:"what-are-strategies",level:2},{value:"Built-in strategies",id:"built-in-strategies",level:2},{value:"RedirectStrategy",id:"redirectstrategy",level:3},{value:"UnauthorizedStrategy",id:"unauthorizedstrategy",level:3},{value:"Creating custom strategies",id:"creating-custom-strategies",level:2}];function d(e){const t={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h1,{id:"strategies",children:"Strategies"}),"\n",(0,r.jsx)(t.p,{children:"In this section, you will learn:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"What strategies are"}),"\n",(0,r.jsx)(t.li,{children:"How to use built-in strategies"}),"\n",(0,r.jsx)(t.li,{children:"How to create custom strategies"}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"what-are-strategies",children:"What are strategies?"}),"\n",(0,r.jsxs)(t.p,{children:["A strategy is an object that listens to the ",(0,r.jsx)(t.code,{children:"MvcEvent::EVENT_DISPATCH_ERROR"})," event. It is used to describe what\nhappens when access to a resource is unauthorized by LmcRbacMvc."]}),"\n",(0,r.jsxs)(t.p,{children:["LmcRbacMvc strategies all check if an ",(0,r.jsx)(t.code,{children:"LmcRbacMvc\\Exception\\UnauthorizedExceptionInterface"})," has been thrown."]}),"\n",(0,r.jsxs)(t.p,{children:["By default, LmcRbacMvc does not register any strategy for you. The best place to register it is in your ",(0,r.jsx)(t.code,{children:"onBootstrap"}),"\nmethod of the ",(0,r.jsx)(t.code,{children:"Module.php"})," class:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\UnauthorizedStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsx)(t.h2,{id:"built-in-strategies",children:"Built-in strategies"}),"\n",(0,r.jsxs)(t.p,{children:["LmcRbacMvc comes with two built-in strategies: ",(0,r.jsx)(t.code,{children:"RedirectStrategy"})," and ",(0,r.jsx)(t.code,{children:"UnauthorizedStrategy"}),"."]}),"\n",(0,r.jsx)(t.h3,{id:"redirectstrategy",children:"RedirectStrategy"}),"\n",(0,r.jsx)(t.p,{children:"This strategy allows your application to redirect any unauthorized request to another route by optionally appending the previous\nURL as a query parameter."}),"\n",(0,r.jsx)(t.p,{children:"To register it, copy-paste this code into your Module.php class:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\RedirectStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["You can configure the strategy using the ",(0,r.jsx)(t.code,{children:"redirect_strategy"})," subkey:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'redirect_strategy' => [\n 'redirect_when_connected' => true,\n 'redirect_to_route_connected' => 'home',\n 'redirect_to_route_disconnected' => 'login',\n 'append_previous_uri' => true,\n 'previous_uri_query_key' => 'redirectTo'\n ],\n ]\n];\n"})}),"\n",(0,r.jsxs)(t.p,{children:["If users try to access an unauthorized resource (eg.: ",(0,r.jsx)(t.a,{href:"http://www.example.com/delete",children:"http://www.example.com/delete"}),'), they will be\nredirected to the "login" route if is not connected and to the "home" route otherwise (it must exist in your route configuration\nof course) with the previous URL appended : ',(0,r.jsx)(t.a,{href:"http://www.example.com/login?redirectTo=http://www.example.com/delete",children:"http://www.example.com/login?redirectTo=http://www.example.com/delete"})]}),"\n",(0,r.jsxs)(t.p,{children:["You can prevent redirection when a user is connected (i.e. so that the user gets a 403 page) by setting ",(0,r.jsx)(t.code,{children:"redirect_when_connected"})," to ",(0,r.jsx)(t.code,{children:"false"}),"."]}),"\n",(0,r.jsx)(t.h3,{id:"unauthorizedstrategy",children:"UnauthorizedStrategy"}),"\n",(0,r.jsx)(t.p,{children:"This strategy allows your application to render a template on any unauthorized request."}),"\n",(0,r.jsx)(t.p,{children:"To register it, copy-paste this code into your Module.php class:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\UnauthorizedStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["You can configure the strategy using the ",(0,r.jsx)(t.code,{children:"unauthorized_strategy"})," subkey:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'unauthorized_strategy' => [\n 'template' => 'error/custom-403'\n ],\n ]\n];\n"})}),"\n",(0,r.jsxs)(t.blockquote,{children:["\n",(0,r.jsxs)(t.p,{children:["By default, LmcRbacMvc uses a template called ",(0,r.jsx)(t.code,{children:"error/403"}),"."]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"creating-custom-strategies",children:"Creating custom strategies"}),"\n",(0,r.jsxs)(t.p,{children:["Creating a custom strategy is rather easy. Let's say we want to create a strategy that integrates with\nthe ",(0,r.jsx)(t.a,{href:"https://github.com/laminas-api-tools/api-tools-api-problem",children:"ApiProblem"})," Laminas Api Tools module:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"namespace Application\\View\\Strategy;\n\nuse Laminas\\Http\\Response as HttpResponse;\nuse Laminas\\Mvc\\MvcEvent;\nuse Laminas\\ApiTools\\ApiProblem\\ApiProblem;\nuse Laminas\\ApiTools\\ApiProblem\\ApiProblemResponse;\nuse LmcRbacMvc\\View\\Strategy\\AbstractStrategy;\nuse LmcRbacMvc\\Exception\\UnauthorizedExceptionInterface;\n\nclass ApiProblemStrategy extends AbstractStrategy\n{\n public function onError(MvcEvent $event)\n {\n // Do nothing if no error or if response is not HTTP response\n if (!($exception = $event->getParam('exception') instanceof UnauthorizedExceptionInterface)\n || ($result = $event->getResult() instanceof HttpResponse)\n || !($response = $event->getResponse() instanceof HttpResponse)\n ) {\n return;\n }\n\n return new ApiProblemResponse(new ApiProblem($exception->getMessage()));\n }\n}\n"})}),"\n",(0,r.jsx)(t.p,{children:"Register your strategy:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(EventInterface $e)\n{\n $e->getTarget()\n ->getEventManager()\n ->attach(new ApiProblemStrategy());\n}\n"})})]})}function p(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>c});var r=n(6540);const s={},i=r.createContext(s);function a(e){const t=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/main.5e3e5263.js b/assets/js/main.5e3e5263.js deleted file mode 100644 index d164d9c4..00000000 --- a/assets/js/main.5e3e5263.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! For license information please see main.5e3e5263.js.LICENSE.txt */ -(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[792],{8328:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});n(6540);var r=n(3259),a=n.n(r),o=n(4054);const i={"01a85c17":[()=>Promise.all([n.e(869),n.e(209)]).then(n.bind(n,9158)),"@theme/BlogTagsListPage",9158],"02def4a7":[()=>n.e(770).then(n.t.bind(n,1966,19)),"/home/runner/work/LmcRbacMvc/LmcRbacMvc/docs/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",1966],"07929a01":[()=>n.e(598).then(n.t.bind(n,8244,19)),"~blog/default/lmcrbacmvc-blog-tags-lmcrbacmvc-876-list.json",8244],"0e384e19":[()=>n.e(976).then(n.bind(n,1512)),"@site/docs/intro.md",1512],17896441:[()=>Promise.all([n.e(869),n.e(533),n.e(401)]).then(n.bind(n,5022)),"@theme/DocItem",5022],"1f391b9e":[()=>Promise.all([n.e(869),n.e(533),n.e(61)]).then(n.bind(n,7973)),"@theme/MDXPage",7973],"22465cd7":[()=>n.e(166).then(n.bind(n,5823)),"@site/docs/role-providers.md",5823],"2fbe5bb1":[()=>n.e(738).then(n.t.bind(n,9421,19)),"~blog/default/lmcrbacmvc-blog-tags-php-31e.json",9421],"378f56c0":[()=>n.e(639).then(n.bind(n,6060)),"@site/blog/2024-02-22-New-documentation.md?truncated=true",6060],"393be207":[()=>n.e(134).then(n.bind(n,6602)),"@site/src/pages/markdown-page.md",6602],"3b8c55ea":[()=>n.e(803).then(n.bind(n,3668)),"@site/docs/installation.md",3668],"4e6224b1":[()=>n.e(107).then(n.bind(n,2618)),"@site/docs/using-the-authorization-service.md",2618],"5b72c13b":[()=>n.e(698).then(n.t.bind(n,4061,19)),"/home/runner/work/LmcRbacMvc/LmcRbacMvc/docs/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",4061],"5ccac0b2":[()=>n.e(734).then(n.t.bind(n,7123,19)),"~blog/default/lmcrbacmvc-blog-tags-laminas-563.json",7123],"5e95c892":[()=>n.e(647).then(n.bind(n,7121)),"@theme/DocsRoot",7121],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"6875c492":[()=>Promise.all([n.e(869),n.e(533),n.e(747),n.e(813)]).then(n.bind(n,3069)),"@theme/BlogTagsPostsPage",3069],"72e14192":[()=>n.e(814).then(n.bind(n,3744)),"@site/docs/quick-start.md",3744],"809dac3e":[()=>n.e(804).then(n.bind(n,4e3)),"@site/blog/2024-02-22-New-documentation.md",4e3],"811b3192":[()=>n.e(750).then(n.t.bind(n,833,19)),"~blog/default/lmcrbacmvc-blog-28f.json",833],"814f3328":[()=>n.e(472).then(n.t.bind(n,5513,19)),"~blog/default/blog-post-list-prop-default.json",5513],"861ecc09":[()=>n.e(700).then(n.t.bind(n,9012,19)),"~blog/default/lmcrbacmvc-blog-archive-253.json",9012],"8b5d4ccc":[()=>n.e(223).then(n.bind(n,82)),"@site/docs/guards.md",82],"935f2afb":[()=>n.e(581).then(n.t.bind(n,5610,19)),"~docs/default/version-current-metadata-prop-751.json",5610],"9de7562b":[()=>n.e(49).then(n.t.bind(n,5330,19)),"~blog/default/lmcrbacmvc-blog-tags-laminas-563-list.json",5330],"9e4087bc":[()=>n.e(711).then(n.bind(n,9331)),"@theme/BlogArchivePage",9331],a6aa9e1f:[()=>Promise.all([n.e(869),n.e(533),n.e(747),n.e(643)]).then(n.bind(n,7785)),"@theme/BlogListPage",7785],a7bd4aaa:[()=>n.e(98).then(n.bind(n,4532)),"@theme/DocVersionRoot",4532],a94703ab:[()=>Promise.all([n.e(869),n.e(48)]).then(n.bind(n,2559)),"@theme/DocRoot",2559],afaab3e7:[()=>n.e(789).then(n.t.bind(n,4261,19)),"~blog/default/lmcrbacmvc-blog-tags-lmcrbacmvc-876.json",4261],b2c2794f:[()=>n.e(438).then(n.t.bind(n,1085,19)),"~blog/default/lmcrbacmvc-blog-tags-tags-87a.json",1085],b5cb822e:[()=>n.e(390).then(n.bind(n,2957)),"@site/blog/2022-08-02-welcome.md?truncated=true",2957],b9207088:[()=>n.e(571).then(n.bind(n,8849)),"@site/docs/cookbook.md",8849],c0482cd2:[()=>n.e(133).then(n.t.bind(n,3228,19)),"~blog/default/lmcrbacmvc-blog-tags-php-31e-list.json",3228],c4f5d8e4:[()=>Promise.all([n.e(869),n.e(634)]).then(n.bind(n,6467)),"@site/src/pages/index.js",6467],ccc49370:[()=>Promise.all([n.e(869),n.e(533),n.e(747),n.e(249)]).then(n.bind(n,4029)),"@theme/BlogPostPage",4029],d9e16301:[()=>n.e(416).then(n.bind(n,5989)),"@site/docs/support.md",5989],e252ba15:[()=>n.e(182).then(n.t.bind(n,2945,19)),"/home/runner/work/LmcRbacMvc/LmcRbacMvc/docs/.docusaurus/docusaurus-plugin-content-blog/default/plugin-route-context-module-100.json",2945],ee696d42:[()=>n.e(18).then(n.bind(n,3531)),"@site/blog/2022-08-02-welcome.md",3531],f6027d55:[()=>n.e(246).then(n.bind(n,9477)),"@site/docs/strategies.md",9477]};var l=n(4848);function s(e){let{error:t,retry:n,pastDelay:r}=e;return t?(0,l.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,l.jsx)("p",{children:String(t)}),(0,l.jsx)("div",{children:(0,l.jsx)("button",{type:"button",onClick:n,children:"Retry"})})]}):r?(0,l.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,l.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,l.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,l.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var c=n(6921),u=n(3102);function d(e,t){if("*"===e)return a()({loading:s,loader:()=>n.e(237).then(n.bind(n,2237)),modules:["@theme/NotFound"],webpack:()=>[2237],render(e,t){const n=e.default;return(0,l.jsx)(u.W,{value:{plugin:{name:"native",id:"default"}},children:(0,l.jsx)(n,{...t})})}});const r=o[`${e}-${t}`],d={},p=[],f=[],m=(0,c.A)(r);return Object.entries(m).forEach((e=>{let[t,n]=e;const r=i[n];r&&(d[t]=r[0],p.push(r[1]),f.push(r[2]))})),a().Map({loading:s,loader:d,modules:p,webpack:()=>f,render(t,n){const a=JSON.parse(JSON.stringify(r));Object.entries(t).forEach((t=>{let[n,r]=t;const o=r.default;if(!o)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof o&&"function"!=typeof o||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{o[e]=r[e]}));let i=a;const l=n.split(".");l.slice(0,-1).forEach((e=>{i=i[e]})),i[l[l.length-1]]=o}));const o=a.__comp;delete a.__comp;const i=a.__context;return delete a.__context,(0,l.jsx)(u.W,{value:i,children:(0,l.jsx)(o,{...a,...n})})}})}const p=[{path:"/lmcrbacmvc/blog",component:d("/lmcrbacmvc/blog","912"),exact:!0},{path:"/lmcrbacmvc/blog/archive",component:d("/lmcrbacmvc/blog/archive","1a5"),exact:!0},{path:"/lmcrbacmvc/blog/new-documentation",component:d("/lmcrbacmvc/blog/new-documentation","40d"),exact:!0},{path:"/lmcrbacmvc/blog/tags",component:d("/lmcrbacmvc/blog/tags","8d5"),exact:!0},{path:"/lmcrbacmvc/blog/tags/laminas",component:d("/lmcrbacmvc/blog/tags/laminas","6b9"),exact:!0},{path:"/lmcrbacmvc/blog/tags/lmcrbacmvc",component:d("/lmcrbacmvc/blog/tags/lmcrbacmvc","ee8"),exact:!0},{path:"/lmcrbacmvc/blog/tags/php",component:d("/lmcrbacmvc/blog/tags/php","c85"),exact:!0},{path:"/lmcrbacmvc/blog/welcome",component:d("/lmcrbacmvc/blog/welcome","9b1"),exact:!0},{path:"/lmcrbacmvc/markdown-page",component:d("/lmcrbacmvc/markdown-page","e21"),exact:!0},{path:"/lmcrbacmvc/docs",component:d("/lmcrbacmvc/docs","582"),routes:[{path:"/lmcrbacmvc/docs",component:d("/lmcrbacmvc/docs","19b"),routes:[{path:"/lmcrbacmvc/docs",component:d("/lmcrbacmvc/docs","42f"),routes:[{path:"/lmcrbacmvc/docs/cookbook",component:d("/lmcrbacmvc/docs/cookbook","b82"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/guards",component:d("/lmcrbacmvc/docs/guards","a0d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/installation",component:d("/lmcrbacmvc/docs/installation","98f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/intro",component:d("/lmcrbacmvc/docs/intro","b63"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/quick-start",component:d("/lmcrbacmvc/docs/quick-start","9c3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/role-providers",component:d("/lmcrbacmvc/docs/role-providers","cb6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/strategies",component:d("/lmcrbacmvc/docs/strategies","5b4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/support",component:d("/lmcrbacmvc/docs/support","d5b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmcrbacmvc/docs/using-the-authorization-service",component:d("/lmcrbacmvc/docs/using-the-authorization-service","2ec"),exact:!0,sidebar:"tutorialSidebar"}]}]}]},{path:"/lmcrbacmvc/",component:d("/lmcrbacmvc/","2bf"),exact:!0},{path:"*",component:d("*")}]},6125:(e,t,n)=>{"use strict";n.d(t,{o:()=>o,x:()=>i});var r=n(6540),a=n(4848);const o=r.createContext(!1);function i(e){let{children:t}=e;const[n,i]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{i(!0)}),[]),(0,a.jsx)(o.Provider,{value:n,children:t})}},8536:(e,t,n)=>{"use strict";var r=n(6540),a=n(5338),o=n(4625),i=n(545),l=n(8193);const s=[n(119),n(6134),n(6294),n(1043)];var c=n(8328),u=n(6347),d=n(2831),p=n(4848);function f(e){let{children:t}=e;return(0,p.jsx)(p.Fragment,{children:t})}var m=n(5260),g=n(4586),h=n(6025),b=n(6342),y=n(1003),v=n(2131),w=n(4090),k=n(2967),x=n(440),S=n(1463);function E(){const{i18n:{currentLocale:e,defaultLocale:t,localeConfigs:n}}=(0,g.A)(),r=(0,v.o)(),a=n[e].htmlLang,o=e=>e.replace("-","_");return(0,p.jsxs)(m.A,{children:[Object.entries(n).map((e=>{let[t,{htmlLang:n}]=e;return(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:n},t)})),(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:"x-default"}),(0,p.jsx)("meta",{property:"og:locale",content:o(a)}),Object.values(n).filter((e=>a!==e.htmlLang)).map((e=>(0,p.jsx)("meta",{property:"og:locale:alternate",content:o(e.htmlLang)},`meta-og-${e.htmlLang}`)))]})}function C(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,g.A)(),r=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,g.A)(),{pathname:r}=(0,u.zy)();return e+(0,x.applyTrailingSlash)((0,h.A)(r),{trailingSlash:n,baseUrl:t})}(),a=t?`${n}${t}`:r;return(0,p.jsxs)(m.A,{children:[(0,p.jsx)("meta",{property:"og:url",content:a}),(0,p.jsx)("link",{rel:"canonical",href:a})]})}function _(){const{i18n:{currentLocale:e}}=(0,g.A)(),{metadata:t,image:n}=(0,b.p)();return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(m.A,{children:[(0,p.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,p.jsx)("body",{className:w.w})]}),n&&(0,p.jsx)(y.be,{image:n}),(0,p.jsx)(C,{}),(0,p.jsx)(E,{}),(0,p.jsx)(S.A,{tag:k.Cy,locale:e}),(0,p.jsx)(m.A,{children:t.map(((e,t)=>(0,p.jsx)("meta",{...e},t)))})]})}const A=new Map;function T(e){if(A.has(e.pathname))return{...e,pathname:A.get(e.pathname)};if((0,d.u)(c.A,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return A.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return A.set(e.pathname,t),{...e,pathname:t}}var j=n(6125),L=n(6988),N=n(205);function R(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{const r=t.default?.[e]??t[e];return r?.(...n)}));return()=>a.forEach((e=>e?.()))}const P=function(e){let{children:t,location:n,previousLocation:r}=e;return(0,N.A)((()=>{r!==n&&(!function(e){let{location:t,previousLocation:n}=e;if(!n)return;const r=t.pathname===n.pathname,a=t.hash===n.hash,o=t.search===n.search;if(r&&a&&!o)return;const{hash:i}=t;if(i){const e=decodeURIComponent(i.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:r}),R("onRouteDidUpdate",{previousLocation:r,location:n}))}),[r,n]),t};function O(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,d.u)(c.A,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class D extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=l.A.canUseDOM?R("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=R("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),O(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return(0,p.jsx)(P,{previousLocation:this.previousLocation,location:t,children:(0,p.jsx)(u.qh,{location:t,render:()=>e})})}}const M=D,I="__docusaurus-base-url-issue-banner-container",F="__docusaurus-base-url-issue-banner",z="__docusaurus-base-url-issue-banner-suggestion-container";function B(e){return`\ndocument.addEventListener('DOMContentLoaded', function maybeInsertBanner() {\n var shouldInsert = typeof window['docusaurus'] === 'undefined';\n shouldInsert && insertBanner();\n});\n\nfunction insertBanner() {\n var bannerContainer = document.createElement('div');\n bannerContainer.id = '${I}';\n var bannerHtml = ${JSON.stringify(function(e){return`\n
\n

Your Docusaurus site did not load properly.

\n

A very common reason is a wrong site baseUrl configuration.

\n

Current configured baseUrl = ${e} ${"/"===e?" (default value)":""}

\n

We suggest trying baseUrl =

\n
\n`}(e)).replace(/{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();const a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(r)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var Y=n(6921);const Z=new Set,X=new Set,J=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,ee={prefetch(e){if(!(e=>!J()&&!X.has(e)&&!Z.has(e))(e))return!1;Z.add(e);const t=(0,d.u)(c.A,e).flatMap((e=>{return t=e.route.path,Object.entries(Q).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,Y.A)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?K(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!J()&&!X.has(e))(e)&&(X.add(e),O(e))},te=Object.freeze(ee),ne=Boolean(!0);if(l.A.canUseDOM){window.docusaurus=te;const e=document.getElementById("__docusaurus"),t=(0,p.jsx)(i.vd,{children:(0,p.jsx)(o.Kd,{children:(0,p.jsx)(W,{})})}),n=(e,t)=>{console.error("Docusaurus React Root onRecoverableError:",e,t)},l=()=>{if(ne)r.startTransition((()=>{a.hydrateRoot(e,t,{onRecoverableError:n})}));else{const o=a.createRoot(e,{onRecoverableError:n});r.startTransition((()=>{o.render(t)}))}};O(window.location.pathname).then(l)}},6988:(e,t,n)=>{"use strict";n.d(t,{o:()=>d,l:()=>p});var r=n(6540),a=n(4784);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/lmcrbacmvc/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/lmcrbacmvc/docs","mainDocId":"intro","docs":[{"id":"cookbook","path":"/lmcrbacmvc/docs/cookbook","sidebar":"tutorialSidebar"},{"id":"guards","path":"/lmcrbacmvc/docs/guards","sidebar":"tutorialSidebar"},{"id":"installation","path":"/lmcrbacmvc/docs/installation","sidebar":"tutorialSidebar"},{"id":"intro","path":"/lmcrbacmvc/docs/intro","sidebar":"tutorialSidebar"},{"id":"quick-start","path":"/lmcrbacmvc/docs/quick-start","sidebar":"tutorialSidebar"},{"id":"role-providers","path":"/lmcrbacmvc/docs/role-providers","sidebar":"tutorialSidebar"},{"id":"strategies","path":"/lmcrbacmvc/docs/strategies","sidebar":"tutorialSidebar"},{"id":"support","path":"/lmcrbacmvc/docs/support","sidebar":"tutorialSidebar"},{"id":"using-the-authorization-service","path":"/lmcrbacmvc/docs/using-the-authorization-service","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/lmcrbacmvc/docs/intro","label":"intro"}}}}],"breadcrumbs":true}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var l=n(2654);const s=JSON.parse('{"docusaurusVersion":"3.1.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.1.1"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"3.1.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.1.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.1.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.1.1"}}}');var c=n(4848);const u={siteConfig:a.default,siteMetadata:s,globalData:o,i18n:i,codeTranslations:l},d=r.createContext(u);function p(e){let{children:t}=e;return(0,c.jsx)(d.Provider,{value:u,children:t})}},7489:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});var r=n(6540),a=n(8193),o=n(5260),i=n(440),l=n(781),s=n(4848);function c(e){let{error:t,tryAgain:n}=e;return(0,s.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,s.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,s.jsx)("button",{type:"button",onClick:n,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,s.jsx)(u,{error:t})]})}function u(e){let{error:t}=e;const n=(0,i.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return(0,s.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:n})}function d(e){let{error:t,tryAgain:n}=e;return(0,s.jsxs)(f,{fallback:()=>(0,s.jsx)(c,{error:t,tryAgain:n}),children:[(0,s.jsx)(o.A,{children:(0,s.jsx)("title",{children:"Page Error"})}),(0,s.jsx)(l.A,{children:(0,s.jsx)(c,{error:t,tryAgain:n})})]})}const p=e=>(0,s.jsx)(d,{...e});class f extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.A.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??p)(e)}return e??null}}},8193:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},5260:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(545),a=n(4848);function o(e){return(0,a.jsx)(r.mg,{...e})}},8774:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});var r=n(6540),a=n(4625),o=n(440),i=n(4586),l=n(6654),s=n(8193),c=n(3427),u=n(6025),d=n(4848);function p(e,t){let{isNavLink:n,to:p,href:f,activeClassName:m,isActive:g,"data-noBrokenLinkCheck":h,autoAddBaseUrl:b=!0,...y}=e;const{siteConfig:{trailingSlash:v,baseUrl:w}}=(0,i.A)(),{withBaseUrl:k}=(0,u.h)(),x=(0,c.A)(),S=(0,r.useRef)(null);(0,r.useImperativeHandle)(t,(()=>S.current));const E=p||f;const C=(0,l.A)(E),_=E?.replace("pathname://","");let A=void 0!==_?(T=_,b&&(e=>e.startsWith("/"))(T)?k(T):T):void 0;var T;A&&C&&(A=(0,o.applyTrailingSlash)(A,{trailingSlash:v,baseUrl:w}));const j=(0,r.useRef)(!1),L=n?a.k2:a.N_,N=s.A.canUseIntersectionObserver,R=(0,r.useRef)(),P=()=>{j.current||null==A||(window.docusaurus.preload(A),j.current=!0)};(0,r.useEffect)((()=>(!N&&C&&null!=A&&window.docusaurus.prefetch(A),()=>{N&&R.current&&R.current.disconnect()})),[R,A,N,C]);const O=A?.startsWith("#")??!1,D=!y.target||"_self"===y.target,M=!A||!C||!D||O;return h||!O&&M||x.collectLink(A),y.id&&x.collectAnchor(y.id),M?(0,d.jsx)("a",{ref:S,href:A,...E&&!C&&{target:"_blank",rel:"noopener noreferrer"},...y}):(0,d.jsx)(L,{...y,onMouseEnter:P,onTouchStart:P,innerRef:e=>{S.current=e,N&&e&&C&&(R.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(R.current.unobserve(e),R.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))}))})),R.current.observe(e))},to:A,...n&&{isActive:g,activeClassName:m}})}const f=r.forwardRef(p)},418:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});const r=()=>null},1312:(e,t,n)=>{"use strict";n.d(t,{A:()=>c,T:()=>s});var r=n(6540),a=n(4848);function o(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var i=n(2654);function l(e){let{id:t,message:n}=e;if(void 0===t&&void 0===n)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[t??n]??n??t}function s(e,t){let{message:n,id:r}=e;return o(l({message:n,id:r}),t)}function c(e){let{children:t,id:n,values:r}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal children",t),new Error("The Docusaurus component only accept simple string values");const i=l({message:t,id:n});return(0,a.jsx)(a.Fragment,{children:o(i,r)})}},7065:(e,t,n)=>{"use strict";n.d(t,{W:()=>r});const r="default"},6654:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>a,z:()=>r})},6025:(e,t,n)=>{"use strict";n.d(t,{A:()=>l,h:()=>i});var r=n(6540),a=n(4586),o=n(6654);function i(){const{siteConfig:{baseUrl:e,url:t}}=(0,a.A)(),n=(0,r.useCallback)(((n,r)=>function(e,t,n,r){let{forcePrependBaseUrl:a=!1,absolute:i=!1}=void 0===r?{}:r;if(!n||n.startsWith("#")||(0,o.z)(n))return n;if(a)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const l=n.startsWith(t)?n:t+n.replace(/^\//,"");return i?e+l:l}(t,e,n,r)),[t,e]);return{withBaseUrl:n}}function l(e,t){void 0===t&&(t={});const{withBaseUrl:n}=i();return n(e,t)}},3427:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(6540);n(4848);const a=r.createContext({collectAnchor:()=>{},collectLink:()=>{}}),o=()=>(0,r.useContext)(a);function i(){return o()}},4586:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6988);function o(){return(0,r.useContext)(a.o)}},2303:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6125);function o(){return(0,r.useContext)(a.o)}},205:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});var r=n(6540);const a=n(8193).A.canUseDOM?r.useLayoutEffect:r.useEffect},6921:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function a(e){const t={};return function e(n,a){Object.entries(n).forEach((n=>{let[o,i]=n;const l=a?`${a}.${o}`:o;r(i)?e(i,l):t[l]=i}))}(e),t}},3102:(e,t,n)=>{"use strict";n.d(t,{W:()=>i,o:()=>o});var r=n(6540),a=n(4848);const o=r.createContext(null);function i(e){let{children:t,value:n}=e;const i=r.useContext(o),l=(0,r.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const r={...t.data,...n?.data};return{plugin:t.plugin,data:r}}({parent:i,value:n})),[i,n]);return(0,a.jsx)(o.Provider,{value:l,children:t})}},4070:(e,t,n)=>{"use strict";n.d(t,{zK:()=>g,vT:()=>p,Gy:()=>u,HW:()=>h,ht:()=>d,r7:()=>m,jh:()=>f});var r=n(6347),a=n(4586),o=n(7065);function i(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,a.A)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const l=e=>e.versions.find((e=>e.isLast));function s(e,t){const n=function(e,t){const n=l(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})))}(e,t),a=n?.docs.find((e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(a.id):{}}}const c={},u=()=>i("docusaurus-plugin-content-docs")??c,d=e=>function(e,t,n){void 0===t&&(t=o.W),void 0===n&&(n={});const r=i(e),a=r?.[t];if(!a&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0});function p(e){void 0===e&&(e={});const t=u(),{pathname:n}=(0,r.zy)();return function(e,t,n){void 0===n&&(n={});const a=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,r.B6)(t,{path:n.path,exact:!1,strict:!1})})),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return o}(t,n,e)}function f(e){return d(e).versions}function m(e){const t=d(e);return l(t)}function g(e){const t=d(e),{pathname:n}=(0,r.zy)();return s(t,n)}function h(e){const t=d(e),{pathname:n}=(0,r.zy)();return function(e,t){const n=l(e);return{latestDocSuggestion:s(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},6294:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(5947),a=n.n(r);a().configure({showSpinner:!1});const o={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{a().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},6134:(e,t,n)=>{"use strict";n.r(t);var r=n(1765),a=n(4784);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{"php"===e&&n(9700),n(8692)(`./prism-${e}`)})),delete globalThis.Prism}(r.My)},1107:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});n(6540);var r=n(4164),a=n(1312),o=n(6342),i=n(8774),l=n(3427);const s={anchorWithStickyNavbar:"anchorWithStickyNavbar_LWe7",anchorWithHideOnScrollNavbar:"anchorWithHideOnScrollNavbar_WYt5"};var c=n(4848);function u(e){let{as:t,id:n,...u}=e;const d=(0,l.A)(),{navbar:{hideOnScroll:p}}=(0,o.p)();if("h1"===t||!n)return(0,c.jsx)(t,{...u,id:void 0});d.collectAnchor(n);const f=(0,a.T)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof u.children?u.children:n});return(0,c.jsxs)(t,{...u,className:(0,r.A)("anchor",p?s.anchorWithHideOnScrollNavbar:s.anchorWithStickyNavbar,u.className),id:n,children:[u.children,(0,c.jsx)(i.A,{className:"hash-link",to:`#${n}`,"aria-label":f,title:f,children:"\u200b"})]})}},3186:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);const r={iconExternalLink:"iconExternalLink_nPIU"};var a=n(4848);function o(e){let{width:t=13.5,height:n=13.5}=e;return(0,a.jsx)("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:r.iconExternalLink,children:(0,a.jsx)("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"})})}},781:(e,t,n)=>{"use strict";n.d(t,{A:()=>ft});var r=n(6540),a=n(4164),o=n(7489),i=n(1003),l=n(6347),s=n(1312),c=n(5062),u=n(4848);const d="__docusaurus_skipToContent_fallback";function p(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function f(){const e=(0,r.useRef)(null),{action:t}=(0,l.W6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&p(t)}),[]);return(0,c.$)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&p(e.current)})),{containerRef:e,onClick:n}}const m=(0,s.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function g(e){const t=e.children??m,{containerRef:n,onClick:r}=f();return(0,u.jsx)("div",{ref:n,role:"region","aria-label":m,children:(0,u.jsx)("a",{...e,href:`#${d}`,onClick:r,children:t})})}var h=n(7559),b=n(4090);const y={skipToContent:"skipToContent_fXgn"};function v(){return(0,u.jsx)(g,{className:y.skipToContent})}var w=n(6342),k=n(5041);function x(e){let{width:t=21,height:n=21,color:r="currentColor",strokeWidth:a=1.2,className:o,...i}=e;return(0,u.jsx)("svg",{viewBox:"0 0 15 15",width:t,height:n,...i,children:(0,u.jsx)("g",{stroke:r,strokeWidth:a,children:(0,u.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}const S={closeButton:"closeButton_CVFx"};function E(e){return(0,u.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,a.A)("clean-btn close",S.closeButton,e.className),children:(0,u.jsx)(x,{width:14,height:14,strokeWidth:3.1})})}const C={content:"content_knG7"};function _(e){const{announcementBar:t}=(0,w.p)(),{content:n}=t;return(0,u.jsx)("div",{...e,className:(0,a.A)(C.content,e.className),dangerouslySetInnerHTML:{__html:n}})}const A={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function T(){const{announcementBar:e}=(0,w.p)(),{isActive:t,close:n}=(0,k.Mj)();if(!t)return null;const{backgroundColor:r,textColor:a,isCloseable:o}=e;return(0,u.jsxs)("div",{className:A.announcementBar,style:{backgroundColor:r,color:a},role:"banner",children:[o&&(0,u.jsx)("div",{className:A.announcementBarPlaceholder}),(0,u.jsx)(_,{className:A.announcementBarContent}),o&&(0,u.jsx)(E,{onClick:n,className:A.announcementBarClose})]})}var j=n(9876),L=n(3104);var N=n(9532),R=n(5600);const P=r.createContext(null);function O(e){let{children:t}=e;const n=function(){const e=(0,j.M)(),t=(0,R.YL)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,N.ZC)(o);return(0,r.useEffect)((()=>{o&&!i&&a(!0)}),[o,i]),(0,r.useEffect)((()=>{o?e.shown||a(!0):a(!1)}),[e.shown,o]),(0,r.useMemo)((()=>[n,a]),[n])}();return(0,u.jsx)(P.Provider,{value:n,children:t})}function D(e){if(e.component){const t=e.component;return(0,u.jsx)(t,{...e.props})}}function M(){const e=(0,r.useContext)(P);if(!e)throw new N.dV("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)((()=>n(!1)),[n]),o=(0,R.YL)();return(0,r.useMemo)((()=>({shown:t,hide:a,content:D(o)})),[a,o,t])}function I(e){let{header:t,primaryMenu:n,secondaryMenu:r}=e;const{shown:o}=M();return(0,u.jsxs)("div",{className:"navbar-sidebar",children:[t,(0,u.jsxs)("div",{className:(0,a.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":o}),children:[(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:n}),(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:r})]})]})}var F=n(5293),z=n(2303);function B(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function $(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}const U={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function q(e){let{className:t,buttonClassName:n,value:r,onChange:o}=e;const i=(0,z.A)(),l=(0,s.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===r?(0,s.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,s.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return(0,u.jsx)("div",{className:(0,a.A)(U.toggle,t),children:(0,u.jsxs)("button",{className:(0,a.A)("clean-btn",U.toggleButton,!i&&U.toggleButtonDisabled,n),type:"button",onClick:()=>o("dark"===r?"light":"dark"),disabled:!i,title:l,"aria-label":l,"aria-live":"polite",children:[(0,u.jsx)(B,{className:(0,a.A)(U.toggleIcon,U.lightToggleIcon)}),(0,u.jsx)($,{className:(0,a.A)(U.toggleIcon,U.darkToggleIcon)})]})})}const H=r.memo(q),G={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function V(e){let{className:t}=e;const n=(0,w.p)().navbar.style,r=(0,w.p)().colorMode.disableSwitch,{colorMode:a,setColorMode:o}=(0,F.G)();return r?null:(0,u.jsx)(H,{className:t,buttonClassName:"dark"===n?G.darkNavbarColorModeToggle:void 0,value:a,onChange:o})}var W=n(3465);function Q(){return(0,u.jsx)(W.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function K(){const e=(0,j.M)();return(0,u.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,u.jsx)(x,{color:"var(--ifm-color-emphasis-600)"})})}function Y(){return(0,u.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,u.jsx)(Q,{}),(0,u.jsx)(V,{className:"margin-right--md"}),(0,u.jsx)(K,{})]})}var Z=n(8774),X=n(6025),J=n(6654);function ee(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var te=n(3186);function ne(e){let{activeBasePath:t,activeBaseRegex:n,to:r,href:a,label:o,html:i,isDropdownLink:l,prependBaseUrlToHref:s,...c}=e;const d=(0,X.A)(r),p=(0,X.A)(t),f=(0,X.A)(a,{forcePrependBaseUrl:!0}),m=o&&a&&!(0,J.A)(a),g=i?{dangerouslySetInnerHTML:{__html:i}}:{children:(0,u.jsxs)(u.Fragment,{children:[o,m&&(0,u.jsx)(te.A,{...l&&{width:12,height:12}})]})};return a?(0,u.jsx)(Z.A,{href:s?f:a,...c,...g}):(0,u.jsx)(Z.A,{to:d,isNavLink:!0,...(t||n)&&{isActive:(e,t)=>n?ee(n,t.pathname):t.pathname.startsWith(p)},...c,...g})}function re(e){let{className:t,isDropdownItem:n=!1,...r}=e;const o=(0,u.jsx)(ne,{className:(0,a.A)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n,...r});return n?(0,u.jsx)("li",{children:o}):o}function ae(e){let{className:t,isDropdownItem:n,...r}=e;return(0,u.jsx)("li",{className:"menu__list-item",children:(0,u.jsx)(ne,{className:(0,a.A)("menu__link",t),...r})})}function oe(e){let{mobile:t=!1,position:n,...r}=e;const a=t?ae:re;return(0,u.jsx)(a,{...r,activeClassName:r.activeClassName??(t?"menu__link--active":"navbar__link--active")})}var ie=n(1422),le=n(9169),se=n(4586);const ce={dropdownNavbarItemMobile:"dropdownNavbarItemMobile_S0Fm"};function ue(e,t){return e.some((e=>function(e,t){return!!(0,le.ys)(e.to,t)||!!ee(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function de(e){let{items:t,position:n,className:o,onClick:i,...l}=e;const s=(0,r.useRef)(null),[c,d]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{s.current&&!s.current.contains(e.target)&&d(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[s]),(0,u.jsxs)("div",{ref:s,className:(0,a.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":c}),children:[(0,u.jsx)(ne,{"aria-haspopup":"true","aria-expanded":c,role:"button",href:l.to?void 0:"#",className:(0,a.A)("navbar__link",o),...l,onClick:l.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),d(!c))},children:l.children??l.label}),(0,u.jsx)("ul",{className:"dropdown__menu",children:t.map(((e,t)=>(0,r.createElement)(Ce,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:t})))})]})}function pe(e){let{items:t,className:n,position:o,onClick:i,...s}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,se.A)(),{pathname:t}=(0,l.zy)();return t.replace(e,"/")}(),d=ue(t,c),{collapsed:p,toggleCollapsed:f,setCollapsed:m}=(0,ie.u)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&m(!d)}),[c,d,m]),(0,u.jsxs)("li",{className:(0,a.A)("menu__list-item",{"menu__list-item--collapsed":p}),children:[(0,u.jsx)(ne,{role:"button",className:(0,a.A)(ce.dropdownNavbarItemMobile,"menu__link menu__link--sublist menu__link--sublist-caret",n),...s,onClick:e=>{e.preventDefault(),f()},children:s.children??s.label}),(0,u.jsx)(ie.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:p,children:t.map(((e,t)=>(0,r.createElement)(Ce,{mobile:!0,isDropdownItem:!0,onClick:i,activeClassName:"menu__link--active",...e,key:t})))})]})}function fe(e){let{mobile:t=!1,...n}=e;const r=t?pe:de;return(0,u.jsx)(r,{...n})}var me=n(2131);function ge(e){let{width:t=20,height:n=20,...r}=e;return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0,...r,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}const he="iconLanguage_nlXk";var be=n(418);const ye={navbarSearchContainer:"navbarSearchContainer_Bca1"};function ve(e){let{children:t,className:n}=e;return(0,u.jsx)("div",{className:(0,a.A)(n,ye.navbarSearchContainer),children:t})}var we=n(4070),ke=n(1754);var xe=n(5597);const Se=e=>e.docs.find((t=>t.id===e.mainDocId));const Ee={default:oe,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:r,queryString:a="",...o}=e;const{i18n:{currentLocale:i,locales:c,localeConfigs:d}}=(0,se.A)(),p=(0,me.o)(),{search:f,hash:m}=(0,l.zy)(),g=[...n,...c.map((e=>{const n=`${`pathname://${p.createUrl({locale:e,fullyQualified:!1})}`}${f}${m}${a}`;return{label:d[e].label,lang:d[e].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:e===i?t?"menu__link--active":"dropdown__link--active":""}})),...r],h=t?(0,s.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):d[i].label;return(0,u.jsx)(fe,{...o,mobile:t,label:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(ge,{className:he}),h]}),items:g})},search:function(e){let{mobile:t,className:n}=e;return t?null:(0,u.jsx)(ve,{className:n,children:(0,u.jsx)(be.A,{})})},dropdown:fe,html:function(e){let{value:t,className:n,mobile:r=!1,isDropdownItem:o=!1}=e;const i=o?"li":"div";return(0,u.jsx)(i,{className:(0,a.A)({navbar__item:!r&&!o,"menu__list-item":r},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:r,...a}=e;const{activeDoc:o}=(0,we.zK)(r),i=(0,ke.QB)(t,r),l=o?.path===i?.path;return null===i||i.unlisted&&!l?null:(0,u.jsx)(oe,{exact:!0,...a,isActive:()=>l||!!o?.sidebar&&o.sidebar===i.sidebar,label:n??i.id,to:i.path})},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:r,...a}=e;const{activeDoc:o}=(0,we.zK)(r),i=(0,ke.fW)(t,r).link;if(!i)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return(0,u.jsx)(oe,{exact:!0,...a,isActive:()=>o?.sidebar===t,label:n??i.label,to:i.path})},docsVersion:function(e){let{label:t,to:n,docsPluginId:r,...a}=e;const o=(0,ke.Vd)(r)[0],i=t??o.label,l=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(o).path;return(0,u.jsx)(oe,{...a,label:i,to:l})},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:r,dropdownItemsBefore:a,dropdownItemsAfter:o,...i}=e;const{search:c,hash:d}=(0,l.zy)(),p=(0,we.zK)(n),f=(0,we.jh)(n),{savePreferredVersionName:m}=(0,xe.g1)(n),g=[...a,...f.map((e=>{const t=p.alternateDocVersions[e.name]??Se(e);return{label:e.label,to:`${t.path}${c}${d}`,isActive:()=>e===p.activeVersion,onClick:()=>m(e.name)}})),...o],h=(0,ke.Vd)(n)[0],b=t&&g.length>1?(0,s.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):h.label,y=t&&g.length>1?void 0:Se(h).path;return g.length<=1?(0,u.jsx)(oe,{...i,mobile:t,label:b,to:y,isActive:r?()=>!1:void 0}):(0,u.jsx)(fe,{...i,mobile:t,label:b,to:y,items:g,isActive:r?()=>!1:void 0})}};function Ce(e){let{type:t,...n}=e;const r=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),a=Ee[r];if(!a)throw new Error(`No NavbarItem component found for type "${t}".`);return(0,u.jsx)(a,{...n})}function _e(){const e=(0,j.M)(),t=(0,w.p)().navbar.items;return(0,u.jsx)("ul",{className:"menu__list",children:t.map(((t,n)=>(0,r.createElement)(Ce,{mobile:!0,...t,onClick:()=>e.toggle(),key:n})))})}function Ae(e){return(0,u.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,u.jsx)(s.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function Te(){const e=0===(0,w.p)().navbar.items.length,t=M();return(0,u.jsxs)(u.Fragment,{children:[!e&&(0,u.jsx)(Ae,{onClick:()=>t.hide()}),t.content]})}function je(){const e=(0,j.M)();var t;return void 0===(t=e.shown)&&(t=!0),(0,r.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?(0,u.jsx)(I,{header:(0,u.jsx)(Y,{}),primaryMenu:(0,u.jsx)(_e,{}),secondaryMenu:(0,u.jsx)(Te,{})}):null}const Le={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Ne(e){return(0,u.jsx)("div",{role:"presentation",...e,className:(0,a.A)("navbar-sidebar__backdrop",e.className)})}function Re(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:o}}=(0,w.p)(),i=(0,j.M)(),{navbarRef:l,isNavbarVisible:d}=function(e){const[t,n]=(0,r.useState)(e),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),i=(0,r.useCallback)((e=>{null!==e&&(o.current=e.getBoundingClientRect().height)}),[]);return(0,L.Mq)(((t,r)=>{let{scrollY:i}=t;if(!e)return;if(i=l?n(!1):i+c{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)})),{navbarRef:i,isNavbarVisible:t}}(n);return(0,u.jsxs)("nav",{ref:l,"aria-label":(0,s.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.A)("navbar","navbar--fixed-top",n&&[Le.navbarHideable,!d&&Le.navbarHidden],{"navbar--dark":"dark"===o,"navbar--primary":"primary"===o,"navbar-sidebar--show":i.shown}),children:[t,(0,u.jsx)(Ne,{onClick:i.toggle}),(0,u.jsx)(je,{})]})}var Pe=n(440);const Oe={errorBoundaryError:"errorBoundaryError_a6uf",errorBoundaryFallback:"errorBoundaryFallback_VBag"};function De(e){return(0,u.jsx)("button",{type:"button",...e,children:(0,u.jsx)(s.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function Me(e){let{error:t}=e;const n=(0,Pe.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return(0,u.jsx)("p",{className:Oe.errorBoundaryError,children:n})}class Ie extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const Fe="right";function ze(e){let{width:t=30,height:n=30,className:r,...a}=e;return(0,u.jsx)("svg",{className:r,width:t,height:n,viewBox:"0 0 30 30","aria-hidden":"true",...a,children:(0,u.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function Be(){const{toggle:e,shown:t}=(0,j.M)();return(0,u.jsx)("button",{onClick:e,"aria-label":(0,s.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button",children:(0,u.jsx)(ze,{})})}const $e={colorModeToggle:"colorModeToggle_DEke"};function Ue(e){let{items:t}=e;return(0,u.jsx)(u.Fragment,{children:t.map(((e,t)=>(0,u.jsx)(Ie,{onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t}),children:(0,u.jsx)(Ce,{...e})},t)))})}function qe(e){let{left:t,right:n}=e;return(0,u.jsxs)("div",{className:"navbar__inner",children:[(0,u.jsx)("div",{className:"navbar__items",children:t}),(0,u.jsx)("div",{className:"navbar__items navbar__items--right",children:n})]})}function He(){const e=(0,j.M)(),t=(0,w.p)().navbar.items,[n,r]=function(e){function t(e){return"left"===(e.position??Fe)}return[e.filter(t),e.filter((e=>!t(e)))]}(t),a=t.find((e=>"search"===e.type));return(0,u.jsx)(qe,{left:(0,u.jsxs)(u.Fragment,{children:[!e.disabled&&(0,u.jsx)(Be,{}),(0,u.jsx)(Q,{}),(0,u.jsx)(Ue,{items:n})]}),right:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(Ue,{items:r}),(0,u.jsx)(V,{className:$e.colorModeToggle}),!a&&(0,u.jsx)(ve,{children:(0,u.jsx)(be.A,{})})]})})}function Ge(){return(0,u.jsx)(Re,{children:(0,u.jsx)(He,{})})}function Ve(e){let{item:t}=e;const{to:n,href:r,label:a,prependBaseUrlToHref:o,...i}=t,l=(0,X.A)(n),s=(0,X.A)(r,{forcePrependBaseUrl:!0});return(0,u.jsxs)(Z.A,{className:"footer__link-item",...r?{href:o?s:r}:{to:l},...i,children:[a,r&&!(0,J.A)(r)&&(0,u.jsx)(te.A,{})]})}function We(e){let{item:t}=e;return t.html?(0,u.jsx)("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:t.html}}):(0,u.jsx)("li",{className:"footer__item",children:(0,u.jsx)(Ve,{item:t})},t.href??t.to)}function Qe(e){let{column:t}=e;return(0,u.jsxs)("div",{className:"col footer__col",children:[(0,u.jsx)("div",{className:"footer__title",children:t.title}),(0,u.jsx)("ul",{className:"footer__items clean-list",children:t.items.map(((e,t)=>(0,u.jsx)(We,{item:e},t)))})]})}function Ke(e){let{columns:t}=e;return(0,u.jsx)("div",{className:"row footer__links",children:t.map(((e,t)=>(0,u.jsx)(Qe,{column:e},t)))})}function Ye(){return(0,u.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function Ze(e){let{item:t}=e;return t.html?(0,u.jsx)("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):(0,u.jsx)(Ve,{item:t})}function Xe(e){let{links:t}=e;return(0,u.jsx)("div",{className:"footer__links text--center",children:(0,u.jsx)("div",{className:"footer__links",children:t.map(((e,n)=>(0,u.jsxs)(r.Fragment,{children:[(0,u.jsx)(Ze,{item:e}),t.length!==n+1&&(0,u.jsx)(Ye,{})]},n)))})})}function Je(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?(0,u.jsx)(Ke,{columns:t}):(0,u.jsx)(Xe,{links:t})}var et=n(1122);const tt={footerLogoLink:"footerLogoLink_BH7S"};function nt(e){let{logo:t}=e;const{withBaseUrl:n}=(0,X.h)(),r={light:n(t.src),dark:n(t.srcDark??t.src)};return(0,u.jsx)(et.A,{className:(0,a.A)("footer__logo",t.className),alt:t.alt,sources:r,width:t.width,height:t.height,style:t.style})}function rt(e){let{logo:t}=e;return t.href?(0,u.jsx)(Z.A,{href:t.href,className:tt.footerLogoLink,target:t.target,children:(0,u.jsx)(nt,{logo:t})}):(0,u.jsx)(nt,{logo:t})}function at(e){let{copyright:t}=e;return(0,u.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function ot(e){let{style:t,links:n,logo:r,copyright:o}=e;return(0,u.jsx)("footer",{className:(0,a.A)("footer",{"footer--dark":"dark"===t}),children:(0,u.jsxs)("div",{className:"container container-fluid",children:[n,(r||o)&&(0,u.jsxs)("div",{className:"footer__bottom text--center",children:[r&&(0,u.jsx)("div",{className:"margin-bottom--sm",children:r}),o]})]})})}function it(){const{footer:e}=(0,w.p)();if(!e)return null;const{copyright:t,links:n,logo:r,style:a}=e;return(0,u.jsx)(ot,{style:a,links:n&&n.length>0&&(0,u.jsx)(Je,{links:n}),logo:r&&(0,u.jsx)(rt,{logo:r}),copyright:t&&(0,u.jsx)(at,{copyright:t})})}const lt=r.memo(it),st=(0,N.fM)([F.a,k.oq,L.Tv,xe.VQ,i.Jx,function(e){let{children:t}=e;return(0,u.jsx)(R.y_,{children:(0,u.jsx)(j.e,{children:(0,u.jsx)(O,{children:t})})})}]);function ct(e){let{children:t}=e;return(0,u.jsx)(st,{children:t})}var ut=n(1107);function dt(e){let{error:t,tryAgain:n}=e;return(0,u.jsx)("main",{className:"container margin-vert--xl",children:(0,u.jsx)("div",{className:"row",children:(0,u.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,u.jsx)(ut.A,{as:"h1",className:"hero__title",children:(0,u.jsx)(s.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,u.jsx)("div",{className:"margin-vert--lg",children:(0,u.jsx)(De,{onClick:n,className:"button button--primary shadow--lw"})}),(0,u.jsx)("hr",{}),(0,u.jsx)("div",{className:"margin-vert--md",children:(0,u.jsx)(Me,{error:t})})]})})})}const pt={mainWrapper:"mainWrapper_z2l0"};function ft(e){const{children:t,noFooter:n,wrapperClassName:r,title:l,description:s}=e;return(0,b.J)(),(0,u.jsxs)(ct,{children:[(0,u.jsx)(i.be,{title:l,description:s}),(0,u.jsx)(v,{}),(0,u.jsx)(T,{}),(0,u.jsx)(Ge,{}),(0,u.jsx)("div",{id:d,className:(0,a.A)(h.G.wrapper.main,pt.mainWrapper,r),children:(0,u.jsx)(o.A,{fallback:e=>(0,u.jsx)(dt,{...e}),children:t})}),!n&&(0,u.jsx)(lt,{})]})}},3465:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});n(6540);var r=n(8774),a=n(6025),o=n(4586),i=n(6342),l=n(1122),s=n(4848);function c(e){let{logo:t,alt:n,imageClassName:r}=e;const o={light:(0,a.A)(t.src),dark:(0,a.A)(t.srcDark||t.src)},i=(0,s.jsx)(l.A,{className:t.className,sources:o,height:t.height,width:t.width,alt:n,style:t.style});return r?(0,s.jsx)("div",{className:r,children:i}):i}function u(e){const{siteConfig:{title:t}}=(0,o.A)(),{navbar:{title:n,logo:l}}=(0,i.p)(),{imageClassName:u,titleClassName:d,...p}=e,f=(0,a.A)(l?.href||"/"),m=n?"":t,g=l?.alt??m;return(0,s.jsxs)(r.A,{to:f,...p,...l?.target&&{target:l.target},children:[l&&(0,s.jsx)(c,{logo:l,alt:g,imageClassName:u}),null!=n&&(0,s.jsx)("b",{className:d,children:n})]})}},1463:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(5260),a=n(4848);function o(e){let{locale:t,version:n,tag:o}=e;const i=t;return(0,a.jsxs)(r.A,{children:[t&&(0,a.jsx)("meta",{name:"docusaurus_locale",content:t}),n&&(0,a.jsx)("meta",{name:"docusaurus_version",content:n}),o&&(0,a.jsx)("meta",{name:"docusaurus_tag",content:o}),i&&(0,a.jsx)("meta",{name:"docsearch:language",content:i}),n&&(0,a.jsx)("meta",{name:"docsearch:version",content:n}),o&&(0,a.jsx)("meta",{name:"docsearch:docusaurus_tag",content:o})]})}},1122:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});var r=n(6540),a=n(4164),o=n(2303),i=n(5293);const l={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};var s=n(4848);function c(e){let{className:t,children:n}=e;const c=(0,o.A)(),{colorMode:u}=(0,i.G)();return(0,s.jsx)(s.Fragment,{children:(c?"dark"===u?["dark"]:["light"]:["light","dark"]).map((e=>{const o=n({theme:e,className:(0,a.A)(t,l.themedComponent,l[`themedComponent--${e}`])});return(0,s.jsx)(r.Fragment,{children:o},e)}))})}function u(e){const{sources:t,className:n,alt:r,...a}=e;return(0,s.jsx)(c,{className:n,children:e=>{let{theme:n,className:o}=e;return(0,s.jsx)("img",{src:t[n],alt:r,className:o,...a})}})}},1422:(e,t,n)=>{"use strict";n.d(t,{N:()=>b,u:()=>c});var r=n(6540),a=n(8193),o=n(205),i=n(3109),l=n(4848);const s="ease-in-out";function c(e){let{initialState:t}=e;const[n,a]=(0,r.useState)(t??!1),o=(0,r.useCallback)((()=>{a((e=>!e))}),[]);return{collapsed:n,setCollapsed:a,toggleCollapsed:o}}const u={display:"none",overflow:"hidden",height:"0px"},d={display:"block",overflow:"visible",height:"auto"};function p(e,t){const n=t?u:d;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function f(e){let{collapsibleRef:t,collapsed:n,animation:a}=e;const o=(0,r.useRef)(!1);(0,r.useEffect)((()=>{const e=t.current;function r(){const t=e.scrollHeight,n=a?.duration??function(e){if((0,i.O)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(t);return{transition:`height ${n}ms ${a?.easing??s}`,height:`${t}px`}}function l(){const t=r();e.style.transition=t.transition,e.style.height=t.height}if(!o.current)return p(e,n),void(o.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(l(),requestAnimationFrame((()=>{e.style.height=u.height,e.style.overflow=u.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{l()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,a])}function m(e){if(!a.A.canUseDOM)return e?u:d}function g(e){let{as:t="div",collapsed:n,children:a,animation:o,onCollapseTransitionEnd:i,className:s,disableSSRStyle:c}=e;const u=(0,r.useRef)(null);return f({collapsibleRef:u,collapsed:n,animation:o}),(0,l.jsx)(t,{ref:u,style:c?void 0:m(n),onTransitionEnd:e=>{"height"===e.propertyName&&(p(u.current,n),i?.(n))},className:s,children:a})}function h(e){let{collapsed:t,...n}=e;const[a,i]=(0,r.useState)(!t),[s,c]=(0,r.useState)(t);return(0,o.A)((()=>{t||i(!0)}),[t]),(0,o.A)((()=>{a&&c(t)}),[a,t]),a?(0,l.jsx)(g,{...n,collapsed:s}):null}function b(e){let{lazy:t,...n}=e;const r=t?h:g;return(0,l.jsx)(r,{...n})}},5041:(e,t,n)=>{"use strict";n.d(t,{Mj:()=>g,oq:()=>m});var r=n(6540),a=n(2303),o=n(9466),i=n(9532),l=n(6342),s=n(4848);const c=(0,o.Wf)("docusaurus.announcement.dismiss"),u=(0,o.Wf)("docusaurus.announcement.id"),d=()=>"true"===c.get(),p=e=>c.set(String(e)),f=r.createContext(null);function m(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,l.p)(),t=(0,a.A)(),[n,o]=(0,r.useState)((()=>!!t&&d()));(0,r.useEffect)((()=>{o(d())}),[]);const i=(0,r.useCallback)((()=>{p(!0),o(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=u.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;u.set(t),r&&p(!1),!r&&d()||o(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:i})),[e,n,i])}();return(0,s.jsx)(f.Provider,{value:n,children:t})}function g(){const e=(0,r.useContext)(f);if(!e)throw new i.dV("AnnouncementBarProvider");return e}},5293:(e,t,n)=>{"use strict";n.d(t,{G:()=>b,a:()=>h});var r=n(6540),a=n(8193),o=n(9532),i=n(9466),l=n(6342),s=n(4848);const c=r.createContext(void 0),u="theme",d=(0,i.Wf)(u),p={light:"light",dark:"dark"},f=e=>e===p.dark?p.dark:p.light,m=e=>a.A.canUseDOM?f(document.documentElement.getAttribute("data-theme")):f(e),g=e=>{d.set(f(e))};function h(e){let{children:t}=e;const n=function(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,l.p)(),[a,o]=(0,r.useState)(m(e));(0,r.useEffect)((()=>{t&&d.del()}),[t]);const i=(0,r.useCallback)((function(t,r){void 0===r&&(r={});const{persist:a=!0}=r;t?(o(t),a&&g(t)):(o(n?window.matchMedia("(prefers-color-scheme: dark)").matches?p.dark:p.light:e),d.del())}),[n,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",f(a))}),[a]),(0,r.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==u)return;const t=d.get();null!==t&&i(f(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,i]);const s=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||s.current?s.current=window.matchMedia("print").matches:i(null)};return e.addListener(r),()=>e.removeListener(r)}),[i,t,n]),(0,r.useMemo)((()=>({colorMode:a,setColorMode:i,get isDarkTheme(){return a===p.dark},setLightTheme(){i(p.light)},setDarkTheme(){i(p.dark)}})),[a,i])}();return(0,s.jsx)(c.Provider,{value:n,children:t})}function b(){const e=(0,r.useContext)(c);if(null==e)throw new o.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},5597:(e,t,n)=>{"use strict";n.d(t,{VQ:()=>b,g1:()=>v});var r=n(6540),a=n(4070),o=n(7065),i=n(6342),l=n(1754),s=n(9532),c=n(9466),u=n(4848);const d=e=>`docs-preferred-version-${e}`,p={save:(e,t,n)=>{(0,c.Wf)(d(e),{persistence:t}).set(n)},read:(e,t)=>(0,c.Wf)(d(e),{persistence:t}).get(),clear:(e,t)=>{(0,c.Wf)(d(e),{persistence:t}).del()}},f=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const m=r.createContext(null);function g(){const e=(0,a.Gy)(),t=(0,i.p)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[o,l]=(0,r.useState)((()=>f(n)));(0,r.useEffect)((()=>{l(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:r}=e;function a(e){const t=p.read(e,n);return r[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(p.clear(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,a(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[o,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){p.save(e,t,n),l((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function h(e){let{children:t}=e;const n=g();return(0,u.jsx)(m.Provider,{value:n,children:t})}function b(e){let{children:t}=e;return l.C5?(0,u.jsx)(h,{children:t}):(0,u.jsx)(u.Fragment,{children:t})}function y(){const e=(0,r.useContext)(m);if(!e)throw new s.dV("DocsPreferredVersionContextProvider");return e}function v(e){void 0===e&&(e=o.W);const t=(0,a.ht)(e),[n,i]=y(),{preferredVersionName:l}=n[e];return{preferredVersion:t.versions.find((e=>e.name===l))??null,savePreferredVersionName:(0,r.useCallback)((t=>{i.savePreferredVersion(e,t)}),[i,e])}}},6588:(e,t,n)=>{"use strict";n.d(t,{V:()=>s,t:()=>c});var r=n(6540),a=n(9532),o=n(4848);const i=Symbol("EmptyContext"),l=r.createContext(i);function s(e){let{children:t,name:n,items:a}=e;const i=(0,r.useMemo)((()=>n&&a?{name:n,items:a}:null),[n,a]);return(0,o.jsx)(l.Provider,{value:i,children:t})}function c(){const e=(0,r.useContext)(l);if(e===i)throw new a.dV("DocsSidebarProvider");return e}},2252:(e,t,n)=>{"use strict";n.d(t,{n:()=>l,r:()=>s});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l(e){let{children:t,version:n}=e;return(0,o.jsx)(i.Provider,{value:n,children:t})}function s(){const e=(0,r.useContext)(i);if(null===e)throw new a.dV("DocsVersionProvider");return e}},9876:(e,t,n)=>{"use strict";n.d(t,{e:()=>f,M:()=>m});var r=n(6540),a=n(5600),o=n(4581),i=n(6347),l=n(9532);function s(e){!function(e){const t=(0,i.W6)(),n=(0,l._q)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}var c=n(6342),u=n(4848);const d=r.createContext(void 0);function p(){const e=function(){const e=(0,a.YL)(),{items:t}=(0,c.p)().navbar;return 0===t.length&&!e.component}(),t=(0,o.l)(),n=!e&&"mobile"===t,[i,l]=(0,r.useState)(!1);s((()=>{if(i)return l(!1),!1}));const u=(0,r.useCallback)((()=>{l((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&l(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:u,shown:i})),[e,n,u,i])}function f(e){let{children:t}=e;const n=p();return(0,u.jsx)(d.Provider,{value:n,children:t})}function m(){const e=r.useContext(d);if(void 0===e)throw new l.dV("NavbarMobileSidebarProvider");return e}},5600:(e,t,n)=>{"use strict";n.d(t,{GX:()=>c,YL:()=>s,y_:()=>l});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l(e){let{children:t}=e;const n=(0,r.useState)({component:null,props:null});return(0,o.jsx)(i.Provider,{value:n,children:t})}function s(){const e=(0,r.useContext)(i);if(!e)throw new a.dV("NavbarSecondaryMenuContentProvider");return e[0]}function c(e){let{component:t,props:n}=e;const o=(0,r.useContext)(i);if(!o)throw new a.dV("NavbarSecondaryMenuContentProvider");const[,l]=o,s=(0,a.Be)(n);return(0,r.useEffect)((()=>{l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((()=>()=>l({component:null,props:null})),[l]),null}},4090:(e,t,n)=>{"use strict";n.d(t,{w:()=>a,J:()=>o});var r=n(6540);const a="navigation-with-keyboard";function o(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},4581:(e,t,n)=>{"use strict";n.d(t,{l:()=>l});var r=n(6540),a=n(8193);const o={desktop:"desktop",mobile:"mobile",ssr:"ssr"},i=996;function l(e){let{desktopBreakpoint:t=i}=void 0===e?{}:e;const[n,l]=(0,r.useState)((()=>"ssr"));return(0,r.useEffect)((()=>{function e(){l(function(e){if(!a.A.canUseDOM)throw new Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?o.desktop:o.mobile}(t))}return e(),window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e)}}),[t]),n}},7559:(e,t,n)=>{"use strict";n.d(t,{G:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{}}},3109:(e,t,n)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{O:()=>r})},1754:(e,t,n)=>{"use strict";n.d(t,{Nr:()=>f,w8:()=>h,C5:()=>p,B5:()=>E,Vd:()=>k,QB:()=>S,fW:()=>x,OF:()=>w,Y:()=>y});var r=n(6540),a=n(6347),o=n(2831),i=n(4070),l=n(5597),s=n(2252),c=n(6588);function u(e){return Array.from(new Set(e))}var d=n(9169);const p=!!i.Gy;function f(e){return"link"!==e.type||e.unlisted?"category"===e.type?function(e){if(e.href&&!e.linkUnlisted)return e.href;for(const t of e.items){const e=f(t);if(e)return e}}(e):void 0:e.href}const m=(e,t)=>void 0!==e&&(0,d.ys)(e,t),g=(e,t)=>e.some((e=>h(e,t)));function h(e,t){return"link"===e.type?m(e.href,t):"category"===e.type&&(m(e.href,t)||g(e.items,t))}function b(e,t){switch(e.type){case"category":return h(e,t)||e.items.some((e=>b(e,t)));case"link":return!e.unlisted||h(e,t);default:return!0}}function y(e,t){return(0,r.useMemo)((()=>e.filter((e=>b(e,t)))),[e,t])}function v(e){let{sidebarItems:t,pathname:n,onlyCategories:r=!1}=e;const a=[];return function e(t){for(const o of t)if("category"===o.type&&((0,d.ys)(o.href,n)||e(o.items))||"link"===o.type&&(0,d.ys)(o.href,n)){return r&&"category"!==o.type||a.unshift(o),!0}return!1}(t),a}function w(){const e=(0,c.t)(),{pathname:t}=(0,a.zy)(),n=(0,i.vT)()?.pluginData.breadcrumbs;return!1!==n&&e?v({sidebarItems:e.items,pathname:t}):null}function k(e){const{activeVersion:t}=(0,i.zK)(e),{preferredVersion:n}=(0,l.g1)(e),a=(0,i.r7)(e);return(0,r.useMemo)((()=>u([t,n,a].filter(Boolean))),[t,n,a])}function x(e,t){const n=k(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map((e=>e[0])).join("\n- ")}`);return r[1]}),[e,n])}function S(e,t){const n=k(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${u(t.map((e=>e.id))).join("\n- ")}`)}return r}),[e,n])}function E(e){let{route:t}=e;const n=(0,a.zy)(),r=(0,s.r)(),i=t.routes,l=i.find((e=>(0,a.B6)(n.pathname,e)));if(!l)return null;const c=l.sidebar,u=c?r.docsSidebars[c]:void 0;return{docElement:(0,o.v)(i),sidebarName:c,sidebarItems:u}}},1003:(e,t,n)=>{"use strict";n.d(t,{e3:()=>f,be:()=>d,Jx:()=>m});var r=n(6540),a=n(4164),o=n(5260),i=n(3102);function l(){const e=r.useContext(i.o);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(6025),c=n(4586);var u=n(4848);function d(e){let{title:t,description:n,keywords:r,image:a,children:i}=e;const l=function(e){const{siteConfig:t}=(0,c.A)(),{title:n,titleDelimiter:r}=t;return e?.trim().length?`${e.trim()} ${r} ${n}`:n}(t),{withBaseUrl:d}=(0,s.h)(),p=a?d(a,{absolute:!0}):void 0;return(0,u.jsxs)(o.A,{children:[t&&(0,u.jsx)("title",{children:l}),t&&(0,u.jsx)("meta",{property:"og:title",content:l}),n&&(0,u.jsx)("meta",{name:"description",content:n}),n&&(0,u.jsx)("meta",{property:"og:description",content:n}),r&&(0,u.jsx)("meta",{name:"keywords",content:Array.isArray(r)?r.join(","):r}),p&&(0,u.jsx)("meta",{property:"og:image",content:p}),p&&(0,u.jsx)("meta",{name:"twitter:image",content:p}),i]})}const p=r.createContext(void 0);function f(e){let{className:t,children:n}=e;const i=r.useContext(p),l=(0,a.A)(i,t);return(0,u.jsxs)(p.Provider,{value:l,children:[(0,u.jsx)(o.A,{children:(0,u.jsx)("html",{className:l})}),n]})}function m(e){let{children:t}=e;const n=l(),r=`plugin-${n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const o=`plugin-id-${n.plugin.id}`;return(0,u.jsx)(f,{className:(0,a.A)(r,o),children:t})}},9532:(e,t,n)=>{"use strict";n.d(t,{Be:()=>c,ZC:()=>l,_q:()=>i,dV:()=>s,fM:()=>u});var r=n(6540),a=n(205),o=n(4848);function i(e){const t=(0,r.useRef)(e);return(0,a.A)((()=>{t.current=e}),[e]),(0,r.useCallback)((function(){return t.current(...arguments)}),[])}function l(e){const t=(0,r.useRef)();return(0,a.A)((()=>{t.current=e})),t.current}class s extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function c(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function u(e){return t=>{let{children:n}=t;return(0,o.jsx)(o.Fragment,{children:e.reduceRight(((e,t)=>(0,o.jsx)(t,{children:e})),n)})}}},9169:(e,t,n)=>{"use strict";n.d(t,{Dt:()=>l,ys:()=>i});var r=n(6540),a=n(8328),o=n(4586);function i(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function l(){const{baseUrl:e}=(0,o.A)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function r(e){return e.path===t&&!0===e.exact}function a(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(r)||e(t.filter(a).flatMap((e=>e.routes??[])))}(n)}({routes:a.A,baseUrl:e})),[e])}},3104:(e,t,n)=>{"use strict";n.d(t,{Mq:()=>p,Tv:()=>c,gk:()=>f});var r=n(6540),a=n(8193),o=n(2303),i=(n(205),n(9532)),l=n(4848);const s=r.createContext(void 0);function c(e){let{children:t}=e;const n=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return(0,l.jsx)(s.Provider,{value:n,children:t})}function u(){const e=(0,r.useContext)(s);if(null==e)throw new i.dV("ScrollControllerProvider");return e}const d=()=>a.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function p(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=u(),a=(0,r.useRef)(d()),o=(0,i._q)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=d();o(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[o,n,...t])}function f(){const e=(0,r.useRef)(null),t=(0,o.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&at&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},2967:(e,t,n)=>{"use strict";n.d(t,{Cy:()=>r,tU:()=>a});n(4586);const r="default";function a(e,t){return`docs-${e}-${t}`}},9466:(e,t,n)=>{"use strict";n.d(t,{Wf:()=>s});n(6540);const r="localStorage";function a(e){let{key:t,oldValue:n,newValue:r,storage:a}=e;if(n===r)return;const o=document.createEvent("StorageEvent");o.initStorageEvent("storage",!1,!1,t,n,r,window.location.href,a),window.dispatchEvent(o)}function o(e){if(void 0===e&&(e=r),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,i||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),i=!0),null}var t}let i=!1;const l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function s(e,t){if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(e);const n=o(t?.persistence);return null===n?l:{get:()=>{try{return n.getItem(e)}catch(t){return console.error(`Docusaurus storage error, can't get key=${e}`,t),null}},set:t=>{try{const r=n.getItem(e);n.setItem(e,t),a({key:e,oldValue:r,newValue:t,storage:n})}catch(r){console.error(`Docusaurus storage error, can't set ${e}=${t}`,r)}},del:()=>{try{const t=n.getItem(e);n.removeItem(e),a({key:e,oldValue:t,newValue:null,storage:n})}catch(t){console.error(`Docusaurus storage error, can't delete key=${e}`,t)}},listen:t=>{try{const r=r=>{r.storageArea===n&&r.key===e&&t(r)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}catch(r){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,r),()=>{}}}}}},2131:(e,t,n)=>{"use strict";n.d(t,{o:()=>i});var r=n(4586),a=n(6347),o=n(440);function i(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:i,currentLocale:l}}=(0,r.A)(),{pathname:s}=(0,a.zy)(),c=(0,o.applyTrailingSlash)(s,{trailingSlash:n,baseUrl:e}),u=l===i?e:e.replace(`/${l}/`,"/"),d=c.replace(e,"");return{createUrl:function(e){let{locale:n,fullyQualified:r}=e;return`${r?t:""}${function(e){return e===i?`${u}`:`${u}${e}/`}(n)}${d}`}}}},5062:(e,t,n)=>{"use strict";n.d(t,{$:()=>i});var r=n(6540),a=n(6347),o=n(9532);function i(e){const t=(0,a.zy)(),n=(0,o.ZC)(t),i=(0,o._q)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},6342:(e,t,n)=>{"use strict";n.d(t,{p:()=>a});var r=n(4586);function a(){return(0,r.A)().siteConfig.themeConfig}},2983:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[a]=e.split(/[#?]/),o="/"===a||a===r?a:(i=a,n?function(e){return e.endsWith("/")?e:`${e}/`}(i):function(e){return e.endsWith("/")?e.slice(0,-1):e}(i));var i;return e.replace(a,o)}},253:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=void 0,t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},440:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="__blog-post-container";var a=n(2983);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(a).default}});var o=n(253);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return o.getErrorCausalChain}})},1513:(e,t,n)=>{"use strict";n.d(t,{zR:()=>w,TM:()=>_,yJ:()=>f,sC:()=>T,AO:()=>p});var r=n(8168);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r=0;p--){var f=i[p];"."===f?o(i,p):".."===f?(o(i,p),d++):d&&(o(i,p),d--)}if(!c)for(;d--;d)i.unshift("..");!c||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(1561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function p(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function f(e,t,n,a){var o;"string"==typeof e?(o=function(e){var t=e||"/",n="",r="",a=t.indexOf("#");-1!==a&&(r=t.substr(a),t=t.substr(0,a));var o=t.indexOf("?");return-1!==o&&(n=t.substr(o),t=t.substr(0,o)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),o.state=t):(void 0===(o=(0,r.A)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;rt?n.splice(t,n.length-t,a):n.push(a),d({action:r,location:a,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",a=f(e,t,g(),w.location);u.confirmTransitionTo(a,r,n,(function(e){e&&(w.entries[w.index]=a,d({action:r,location:a}))}))},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(e){var t=w.index+e;return t>=0&&t{"use strict";var r=n(4363),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||a}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=f(n);a&&a!==m&&e(t,a,r)}var i=u(n);d&&(i=i.concat(d(n)));for(var l=s(t),g=s(n),h=0;h{"use strict";e.exports=function(e,t,n,r,a,o,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,a,o,i,l],u=0;(s=new Error(t.replace(/%s/g,(function(){return c[u++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},4634:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},119:(e,t,n)=>{"use strict";n.r(t)},1043:(e,t,n)=>{"use strict";n.r(t)},5947:function(e,t,n){var r,a;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};function a(e,t,n){return en?n:e}function o(e){return 100*(-1+e)}function i(e,t,n){var a;return(a="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,r.minimum,1),n.status=1===e?null:e;var o=n.render(!t),c=o.querySelector(r.barSelector),u=r.speed,d=r.easing;return o.offsetWidth,l((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(c,i(e,u,d)),1===e?(s(o,{transition:"none",opacity:1}),o.offsetWidth,setTimeout((function(){s(o,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),u)}),u)):setTimeout(t,u)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var a,i=t.querySelector(r.barSelector),l=e?"-100":o(n.status||0),c=document.querySelector(r.parent);return s(i,{transition:"all 0 linear",transform:"translate3d("+l+"%,0,0)"}),r.showSpinner||(a=t.querySelector(r.spinnerSelector))&&f(a),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&f(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}function a(e){return e=n(e),t[e]||(t[e]=r(e))}function o(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,r,a=arguments;if(2==a.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,a[1],a[2])}}();function c(e,t){return("string"==typeof e?e:p(e)).indexOf(" "+t+" ")>=0}function u(e,t){var n=p(e),r=n+t;c(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=p(e);c(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function p(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function f(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=a)},6969:e=>{e.exports&&(e.exports={core:{meta:{path:"components/prism-core.js",option:"mandatory"},core:"Core"},themes:{meta:{path:"themes/{id}.css",link:"index.html?theme={id}",exclusive:!0},prism:{title:"Default",option:"default"},"prism-dark":"Dark","prism-funky":"Funky","prism-okaidia":{title:"Okaidia",owner:"ocodia"},"prism-twilight":{title:"Twilight",owner:"remybach"},"prism-coy":{title:"Coy",owner:"tshedor"},"prism-solarizedlight":{title:"Solarized Light",owner:"hectormatos2011 "},"prism-tomorrow":{title:"Tomorrow Night",owner:"Rosey"}},languages:{meta:{path:"components/prism-{id}",noCSS:!0,examplesPath:"examples/prism-{id}",addCheckAll:!0},markup:{title:"Markup",alias:["html","xml","svg","mathml","ssml","atom","rss"],aliasTitles:{html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",ssml:"SSML",atom:"Atom",rss:"RSS"},option:"default"},css:{title:"CSS",option:"default",modify:"markup"},clike:{title:"C-like",option:"default"},javascript:{title:"JavaScript",require:"clike",modify:"markup",optional:"regex",alias:"js",option:"default"},abap:{title:"ABAP",owner:"dellagustin"},abnf:{title:"ABNF",owner:"RunDevelopment"},actionscript:{title:"ActionScript",require:"javascript",modify:"markup",owner:"Golmote"},ada:{title:"Ada",owner:"Lucretia"},agda:{title:"Agda",owner:"xy-ren"},al:{title:"AL",owner:"RunDevelopment"},antlr4:{title:"ANTLR4",alias:"g4",owner:"RunDevelopment"},apacheconf:{title:"Apache Configuration",owner:"GuiTeK"},apex:{title:"Apex",require:["clike","sql"],owner:"RunDevelopment"},apl:{title:"APL",owner:"ngn"},applescript:{title:"AppleScript",owner:"Golmote"},aql:{title:"AQL",owner:"RunDevelopment"},arduino:{title:"Arduino",require:"cpp",alias:"ino",owner:"dkern"},arff:{title:"ARFF",owner:"Golmote"},armasm:{title:"ARM Assembly",alias:"arm-asm",owner:"RunDevelopment"},arturo:{title:"Arturo",alias:"art",optional:["bash","css","javascript","markup","markdown","sql"],owner:"drkameleon"},asciidoc:{alias:"adoc",title:"AsciiDoc",owner:"Golmote"},aspnet:{title:"ASP.NET (C#)",require:["markup","csharp"],owner:"nauzilus"},asm6502:{title:"6502 Assembly",owner:"kzurawel"},asmatmel:{title:"Atmel AVR Assembly",owner:"cerkit"},autohotkey:{title:"AutoHotkey",owner:"aviaryan"},autoit:{title:"AutoIt",owner:"Golmote"},avisynth:{title:"AviSynth",alias:"avs",owner:"Zinfidel"},"avro-idl":{title:"Avro IDL",alias:"avdl",owner:"RunDevelopment"},awk:{title:"AWK",alias:"gawk",aliasTitles:{gawk:"GAWK"},owner:"RunDevelopment"},bash:{title:"Bash",alias:["sh","shell"],aliasTitles:{sh:"Shell",shell:"Shell"},owner:"zeitgeist87"},basic:{title:"BASIC",owner:"Golmote"},batch:{title:"Batch",owner:"Golmote"},bbcode:{title:"BBcode",alias:"shortcode",aliasTitles:{shortcode:"Shortcode"},owner:"RunDevelopment"},bbj:{title:"BBj",owner:"hyyan"},bicep:{title:"Bicep",owner:"johnnyreilly"},birb:{title:"Birb",require:"clike",owner:"Calamity210"},bison:{title:"Bison",require:"c",owner:"Golmote"},bnf:{title:"BNF",alias:"rbnf",aliasTitles:{rbnf:"RBNF"},owner:"RunDevelopment"},bqn:{title:"BQN",owner:"yewscion"},brainfuck:{title:"Brainfuck",owner:"Golmote"},brightscript:{title:"BrightScript",owner:"RunDevelopment"},bro:{title:"Bro",owner:"wayward710"},bsl:{title:"BSL (1C:Enterprise)",alias:"oscript",aliasTitles:{oscript:"OneScript"},owner:"Diversus23"},c:{title:"C",require:"clike",owner:"zeitgeist87"},csharp:{title:"C#",require:"clike",alias:["cs","dotnet"],owner:"mvalipour"},cpp:{title:"C++",require:"c",owner:"zeitgeist87"},cfscript:{title:"CFScript",require:"clike",alias:"cfc",owner:"mjclemente"},chaiscript:{title:"ChaiScript",require:["clike","cpp"],owner:"RunDevelopment"},cil:{title:"CIL",owner:"sbrl"},cilkc:{title:"Cilk/C",require:"c",alias:"cilk-c",owner:"OpenCilk"},cilkcpp:{title:"Cilk/C++",require:"cpp",alias:["cilk-cpp","cilk"],owner:"OpenCilk"},clojure:{title:"Clojure",owner:"troglotit"},cmake:{title:"CMake",owner:"mjrogozinski"},cobol:{title:"COBOL",owner:"RunDevelopment"},coffeescript:{title:"CoffeeScript",require:"javascript",alias:"coffee",owner:"R-osey"},concurnas:{title:"Concurnas",alias:"conc",owner:"jasontatton"},csp:{title:"Content-Security-Policy",owner:"ScottHelme"},cooklang:{title:"Cooklang",owner:"ahue"},coq:{title:"Coq",owner:"RunDevelopment"},crystal:{title:"Crystal",require:"ruby",owner:"MakeNowJust"},"css-extras":{title:"CSS Extras",require:"css",modify:"css",owner:"milesj"},csv:{title:"CSV",owner:"RunDevelopment"},cue:{title:"CUE",owner:"RunDevelopment"},cypher:{title:"Cypher",owner:"RunDevelopment"},d:{title:"D",require:"clike",owner:"Golmote"},dart:{title:"Dart",require:"clike",owner:"Golmote"},dataweave:{title:"DataWeave",owner:"machaval"},dax:{title:"DAX",owner:"peterbud"},dhall:{title:"Dhall",owner:"RunDevelopment"},diff:{title:"Diff",owner:"uranusjr"},django:{title:"Django/Jinja2",require:"markup-templating",alias:"jinja2",owner:"romanvm"},"dns-zone-file":{title:"DNS zone file",owner:"RunDevelopment",alias:"dns-zone"},docker:{title:"Docker",alias:"dockerfile",owner:"JustinBeckwith"},dot:{title:"DOT (Graphviz)",alias:"gv",optional:"markup",owner:"RunDevelopment"},ebnf:{title:"EBNF",owner:"RunDevelopment"},editorconfig:{title:"EditorConfig",owner:"osipxd"},eiffel:{title:"Eiffel",owner:"Conaclos"},ejs:{title:"EJS",require:["javascript","markup-templating"],owner:"RunDevelopment",alias:"eta",aliasTitles:{eta:"Eta"}},elixir:{title:"Elixir",owner:"Golmote"},elm:{title:"Elm",owner:"zwilias"},etlua:{title:"Embedded Lua templating",require:["lua","markup-templating"],owner:"RunDevelopment"},erb:{title:"ERB",require:["ruby","markup-templating"],owner:"Golmote"},erlang:{title:"Erlang",owner:"Golmote"},"excel-formula":{title:"Excel Formula",alias:["xlsx","xls"],owner:"RunDevelopment"},fsharp:{title:"F#",require:"clike",owner:"simonreynolds7"},factor:{title:"Factor",owner:"catb0t"},false:{title:"False",owner:"edukisto"},"firestore-security-rules":{title:"Firestore security rules",require:"clike",owner:"RunDevelopment"},flow:{title:"Flow",require:"javascript",owner:"Golmote"},fortran:{title:"Fortran",owner:"Golmote"},ftl:{title:"FreeMarker Template Language",require:"markup-templating",owner:"RunDevelopment"},gml:{title:"GameMaker Language",alias:"gamemakerlanguage",require:"clike",owner:"LiarOnce"},gap:{title:"GAP (CAS)",owner:"RunDevelopment"},gcode:{title:"G-code",owner:"RunDevelopment"},gdscript:{title:"GDScript",owner:"RunDevelopment"},gedcom:{title:"GEDCOM",owner:"Golmote"},gettext:{title:"gettext",alias:"po",owner:"RunDevelopment"},gherkin:{title:"Gherkin",owner:"hason"},git:{title:"Git",owner:"lgiraudel"},glsl:{title:"GLSL",require:"c",owner:"Golmote"},gn:{title:"GN",alias:"gni",owner:"RunDevelopment"},"linker-script":{title:"GNU Linker Script",alias:"ld",owner:"RunDevelopment"},go:{title:"Go",require:"clike",owner:"arnehormann"},"go-module":{title:"Go module",alias:"go-mod",owner:"RunDevelopment"},gradle:{title:"Gradle",require:"clike",owner:"zeabdelkhalek-badido18"},graphql:{title:"GraphQL",optional:"markdown",owner:"Golmote"},groovy:{title:"Groovy",require:"clike",owner:"robfletcher"},haml:{title:"Haml",require:"ruby",optional:["css","css-extras","coffeescript","erb","javascript","less","markdown","scss","textile"],owner:"Golmote"},handlebars:{title:"Handlebars",require:"markup-templating",alias:["hbs","mustache"],aliasTitles:{mustache:"Mustache"},owner:"Golmote"},haskell:{title:"Haskell",alias:"hs",owner:"bholst"},haxe:{title:"Haxe",require:"clike",optional:"regex",owner:"Golmote"},hcl:{title:"HCL",owner:"outsideris"},hlsl:{title:"HLSL",require:"c",owner:"RunDevelopment"},hoon:{title:"Hoon",owner:"matildepark"},http:{title:"HTTP",optional:["csp","css","hpkp","hsts","javascript","json","markup","uri"],owner:"danielgtaylor"},hpkp:{title:"HTTP Public-Key-Pins",owner:"ScottHelme"},hsts:{title:"HTTP Strict-Transport-Security",owner:"ScottHelme"},ichigojam:{title:"IchigoJam",owner:"BlueCocoa"},icon:{title:"Icon",owner:"Golmote"},"icu-message-format":{title:"ICU Message Format",owner:"RunDevelopment"},idris:{title:"Idris",alias:"idr",owner:"KeenS",require:"haskell"},ignore:{title:".ignore",owner:"osipxd",alias:["gitignore","hgignore","npmignore"],aliasTitles:{gitignore:".gitignore",hgignore:".hgignore",npmignore:".npmignore"}},inform7:{title:"Inform 7",owner:"Golmote"},ini:{title:"Ini",owner:"aviaryan"},io:{title:"Io",owner:"AlesTsurko"},j:{title:"J",owner:"Golmote"},java:{title:"Java",require:"clike",owner:"sherblot"},javadoc:{title:"JavaDoc",require:["markup","java","javadoclike"],modify:"java",optional:"scala",owner:"RunDevelopment"},javadoclike:{title:"JavaDoc-like",modify:["java","javascript","php"],owner:"RunDevelopment"},javastacktrace:{title:"Java stack trace",owner:"RunDevelopment"},jexl:{title:"Jexl",owner:"czosel"},jolie:{title:"Jolie",require:"clike",owner:"thesave"},jq:{title:"JQ",owner:"RunDevelopment"},jsdoc:{title:"JSDoc",require:["javascript","javadoclike","typescript"],modify:"javascript",optional:["actionscript","coffeescript"],owner:"RunDevelopment"},"js-extras":{title:"JS Extras",require:"javascript",modify:"javascript",optional:["actionscript","coffeescript","flow","n4js","typescript"],owner:"RunDevelopment"},json:{title:"JSON",alias:"webmanifest",aliasTitles:{webmanifest:"Web App Manifest"},owner:"CupOfTea696"},json5:{title:"JSON5",require:"json",owner:"RunDevelopment"},jsonp:{title:"JSONP",require:"json",owner:"RunDevelopment"},jsstacktrace:{title:"JS stack trace",owner:"sbrl"},"js-templates":{title:"JS Templates",require:"javascript",modify:"javascript",optional:["css","css-extras","graphql","markdown","markup","sql"],owner:"RunDevelopment"},julia:{title:"Julia",owner:"cdagnino"},keepalived:{title:"Keepalived Configure",owner:"dev-itsheng"},keyman:{title:"Keyman",owner:"mcdurdin"},kotlin:{title:"Kotlin",alias:["kt","kts"],aliasTitles:{kts:"Kotlin Script"},require:"clike",owner:"Golmote"},kumir:{title:"KuMir (\u041a\u0443\u041c\u0438\u0440)",alias:"kum",owner:"edukisto"},kusto:{title:"Kusto",owner:"RunDevelopment"},latex:{title:"LaTeX",alias:["tex","context"],aliasTitles:{tex:"TeX",context:"ConTeXt"},owner:"japborst"},latte:{title:"Latte",require:["clike","markup-templating","php"],owner:"nette"},less:{title:"Less",require:"css",optional:"css-extras",owner:"Golmote"},lilypond:{title:"LilyPond",require:"scheme",alias:"ly",owner:"RunDevelopment"},liquid:{title:"Liquid",require:"markup-templating",owner:"cinhtau"},lisp:{title:"Lisp",alias:["emacs","elisp","emacs-lisp"],owner:"JuanCaicedo"},livescript:{title:"LiveScript",owner:"Golmote"},llvm:{title:"LLVM IR",owner:"porglezomp"},log:{title:"Log file",optional:"javastacktrace",owner:"RunDevelopment"},lolcode:{title:"LOLCODE",owner:"Golmote"},lua:{title:"Lua",owner:"Golmote"},magma:{title:"Magma (CAS)",owner:"RunDevelopment"},makefile:{title:"Makefile",owner:"Golmote"},markdown:{title:"Markdown",require:"markup",optional:"yaml",alias:"md",owner:"Golmote"},"markup-templating":{title:"Markup templating",require:"markup",owner:"Golmote"},mata:{title:"Mata",owner:"RunDevelopment"},matlab:{title:"MATLAB",owner:"Golmote"},maxscript:{title:"MAXScript",owner:"RunDevelopment"},mel:{title:"MEL",owner:"Golmote"},mermaid:{title:"Mermaid",owner:"RunDevelopment"},metafont:{title:"METAFONT",owner:"LaeriExNihilo"},mizar:{title:"Mizar",owner:"Golmote"},mongodb:{title:"MongoDB",owner:"airs0urce",require:"javascript"},monkey:{title:"Monkey",owner:"Golmote"},moonscript:{title:"MoonScript",alias:"moon",owner:"RunDevelopment"},n1ql:{title:"N1QL",owner:"TMWilds"},n4js:{title:"N4JS",require:"javascript",optional:"jsdoc",alias:"n4jsd",owner:"bsmith-n4"},"nand2tetris-hdl":{title:"Nand To Tetris HDL",owner:"stephanmax"},naniscript:{title:"Naninovel Script",owner:"Elringus",alias:"nani"},nasm:{title:"NASM",owner:"rbmj"},neon:{title:"NEON",owner:"nette"},nevod:{title:"Nevod",owner:"nezaboodka"},nginx:{title:"nginx",owner:"volado"},nim:{title:"Nim",owner:"Golmote"},nix:{title:"Nix",owner:"Golmote"},nsis:{title:"NSIS",owner:"idleberg"},objectivec:{title:"Objective-C",require:"c",alias:"objc",owner:"uranusjr"},ocaml:{title:"OCaml",owner:"Golmote"},odin:{title:"Odin",owner:"edukisto"},opencl:{title:"OpenCL",require:"c",modify:["c","cpp"],owner:"Milania1"},openqasm:{title:"OpenQasm",alias:"qasm",owner:"RunDevelopment"},oz:{title:"Oz",owner:"Golmote"},parigp:{title:"PARI/GP",owner:"Golmote"},parser:{title:"Parser",require:"markup",owner:"Golmote"},pascal:{title:"Pascal",alias:"objectpascal",aliasTitles:{objectpascal:"Object Pascal"},owner:"Golmote"},pascaligo:{title:"Pascaligo",owner:"DefinitelyNotAGoat"},psl:{title:"PATROL Scripting Language",owner:"bertysentry"},pcaxis:{title:"PC-Axis",alias:"px",owner:"RunDevelopment"},peoplecode:{title:"PeopleCode",alias:"pcode",owner:"RunDevelopment"},perl:{title:"Perl",owner:"Golmote"},php:{title:"PHP",require:"markup-templating",owner:"milesj"},phpdoc:{title:"PHPDoc",require:["php","javadoclike"],modify:"php",owner:"RunDevelopment"},"php-extras":{title:"PHP Extras",require:"php",modify:"php",owner:"milesj"},"plant-uml":{title:"PlantUML",alias:"plantuml",owner:"RunDevelopment"},plsql:{title:"PL/SQL",require:"sql",owner:"Golmote"},powerquery:{title:"PowerQuery",alias:["pq","mscript"],owner:"peterbud"},powershell:{title:"PowerShell",owner:"nauzilus"},processing:{title:"Processing",require:"clike",owner:"Golmote"},prolog:{title:"Prolog",owner:"Golmote"},promql:{title:"PromQL",owner:"arendjr"},properties:{title:".properties",owner:"Golmote"},protobuf:{title:"Protocol Buffers",require:"clike",owner:"just-boris"},pug:{title:"Pug",require:["markup","javascript"],optional:["coffeescript","ejs","handlebars","less","livescript","markdown","scss","stylus","twig"],owner:"Golmote"},puppet:{title:"Puppet",owner:"Golmote"},pure:{title:"Pure",optional:["c","cpp","fortran"],owner:"Golmote"},purebasic:{title:"PureBasic",require:"clike",alias:"pbfasm",owner:"HeX0R101"},purescript:{title:"PureScript",require:"haskell",alias:"purs",owner:"sriharshachilakapati"},python:{title:"Python",alias:"py",owner:"multipetros"},qsharp:{title:"Q#",require:"clike",alias:"qs",owner:"fedonman"},q:{title:"Q (kdb+ database)",owner:"Golmote"},qml:{title:"QML",require:"javascript",owner:"RunDevelopment"},qore:{title:"Qore",require:"clike",owner:"temnroegg"},r:{title:"R",owner:"Golmote"},racket:{title:"Racket",require:"scheme",alias:"rkt",owner:"RunDevelopment"},cshtml:{title:"Razor C#",alias:"razor",require:["markup","csharp"],optional:["css","css-extras","javascript","js-extras"],owner:"RunDevelopment"},jsx:{title:"React JSX",require:["markup","javascript"],optional:["jsdoc","js-extras","js-templates"],owner:"vkbansal"},tsx:{title:"React TSX",require:["jsx","typescript"]},reason:{title:"Reason",require:"clike",owner:"Golmote"},regex:{title:"Regex",owner:"RunDevelopment"},rego:{title:"Rego",owner:"JordanSh"},renpy:{title:"Ren'py",alias:"rpy",owner:"HyuchiaDiego"},rescript:{title:"ReScript",alias:"res",owner:"vmarcosp"},rest:{title:"reST (reStructuredText)",owner:"Golmote"},rip:{title:"Rip",owner:"ravinggenius"},roboconf:{title:"Roboconf",owner:"Golmote"},robotframework:{title:"Robot Framework",alias:"robot",owner:"RunDevelopment"},ruby:{title:"Ruby",require:"clike",alias:"rb",owner:"samflores"},rust:{title:"Rust",owner:"Golmote"},sas:{title:"SAS",optional:["groovy","lua","sql"],owner:"Golmote"},sass:{title:"Sass (Sass)",require:"css",optional:"css-extras",owner:"Golmote"},scss:{title:"Sass (SCSS)",require:"css",optional:"css-extras",owner:"MoOx"},scala:{title:"Scala",require:"java",owner:"jozic"},scheme:{title:"Scheme",owner:"bacchus123"},"shell-session":{title:"Shell session",require:"bash",alias:["sh-session","shellsession"],owner:"RunDevelopment"},smali:{title:"Smali",owner:"RunDevelopment"},smalltalk:{title:"Smalltalk",owner:"Golmote"},smarty:{title:"Smarty",require:"markup-templating",optional:"php",owner:"Golmote"},sml:{title:"SML",alias:"smlnj",aliasTitles:{smlnj:"SML/NJ"},owner:"RunDevelopment"},solidity:{title:"Solidity (Ethereum)",alias:"sol",require:"clike",owner:"glachaud"},"solution-file":{title:"Solution file",alias:"sln",owner:"RunDevelopment"},soy:{title:"Soy (Closure Template)",require:"markup-templating",owner:"Golmote"},sparql:{title:"SPARQL",require:"turtle",owner:"Triply-Dev",alias:"rq"},"splunk-spl":{title:"Splunk SPL",owner:"RunDevelopment"},sqf:{title:"SQF: Status Quo Function (Arma 3)",require:"clike",owner:"RunDevelopment"},sql:{title:"SQL",owner:"multipetros"},squirrel:{title:"Squirrel",require:"clike",owner:"RunDevelopment"},stan:{title:"Stan",owner:"RunDevelopment"},stata:{title:"Stata Ado",require:["mata","java","python"],owner:"RunDevelopment"},iecst:{title:"Structured Text (IEC 61131-3)",owner:"serhioromano"},stylus:{title:"Stylus",owner:"vkbansal"},supercollider:{title:"SuperCollider",alias:"sclang",owner:"RunDevelopment"},swift:{title:"Swift",owner:"chrischares"},systemd:{title:"Systemd configuration file",owner:"RunDevelopment"},"t4-templating":{title:"T4 templating",owner:"RunDevelopment"},"t4-cs":{title:"T4 Text Templates (C#)",require:["t4-templating","csharp"],alias:"t4",owner:"RunDevelopment"},"t4-vb":{title:"T4 Text Templates (VB)",require:["t4-templating","vbnet"],owner:"RunDevelopment"},tap:{title:"TAP",owner:"isaacs",require:"yaml"},tcl:{title:"Tcl",owner:"PeterChaplin"},tt2:{title:"Template Toolkit 2",require:["clike","markup-templating"],owner:"gflohr"},textile:{title:"Textile",require:"markup",optional:"css",owner:"Golmote"},toml:{title:"TOML",owner:"RunDevelopment"},tremor:{title:"Tremor",alias:["trickle","troy"],owner:"darach",aliasTitles:{trickle:"trickle",troy:"troy"}},turtle:{title:"Turtle",alias:"trig",aliasTitles:{trig:"TriG"},owner:"jakubklimek"},twig:{title:"Twig",require:"markup-templating",owner:"brandonkelly"},typescript:{title:"TypeScript",require:"javascript",optional:"js-templates",alias:"ts",owner:"vkbansal"},typoscript:{title:"TypoScript",alias:"tsconfig",aliasTitles:{tsconfig:"TSConfig"},owner:"dkern"},unrealscript:{title:"UnrealScript",alias:["uscript","uc"],owner:"RunDevelopment"},uorazor:{title:"UO Razor Script",owner:"jaseowns"},uri:{title:"URI",alias:"url",aliasTitles:{url:"URL"},owner:"RunDevelopment"},v:{title:"V",require:"clike",owner:"taggon"},vala:{title:"Vala",require:"clike",optional:"regex",owner:"TemplarVolk"},vbnet:{title:"VB.Net",require:"basic",owner:"Bigsby"},velocity:{title:"Velocity",require:"markup",owner:"Golmote"},verilog:{title:"Verilog",owner:"a-rey"},vhdl:{title:"VHDL",owner:"a-rey"},vim:{title:"vim",owner:"westonganger"},"visual-basic":{title:"Visual Basic",alias:["vb","vba"],aliasTitles:{vba:"VBA"},owner:"Golmote"},warpscript:{title:"WarpScript",owner:"RunDevelopment"},wasm:{title:"WebAssembly",owner:"Golmote"},"web-idl":{title:"Web IDL",alias:"webidl",owner:"RunDevelopment"},wgsl:{title:"WGSL",owner:"Dr4gonthree"},wiki:{title:"Wiki markup",require:"markup",owner:"Golmote"},wolfram:{title:"Wolfram language",alias:["mathematica","nb","wl"],aliasTitles:{mathematica:"Mathematica",nb:"Mathematica Notebook"},owner:"msollami"},wren:{title:"Wren",owner:"clsource"},xeora:{title:"Xeora",require:"markup",alias:"xeoracube",aliasTitles:{xeoracube:"XeoraCube"},owner:"freakmaxi"},"xml-doc":{title:"XML doc (.net)",require:"markup",modify:["csharp","fsharp","vbnet"],owner:"RunDevelopment"},xojo:{title:"Xojo (REALbasic)",owner:"Golmote"},xquery:{title:"XQuery",require:"markup",owner:"Golmote"},yaml:{title:"YAML",alias:"yml",owner:"hason"},yang:{title:"YANG",owner:"RunDevelopment"},zig:{title:"Zig",owner:"RunDevelopment"}},plugins:{meta:{path:"plugins/{id}/prism-{id}",link:"plugins/{id}/"},"line-highlight":{title:"Line Highlight",description:"Highlights specific lines and/or line ranges."},"line-numbers":{title:"Line Numbers",description:"Line number at the beginning of code lines.",owner:"kuba-kubula"},"show-invisibles":{title:"Show Invisibles",description:"Show hidden characters such as tabs and line breaks.",optional:["autolinker","data-uri-highlight"]},autolinker:{title:"Autolinker",description:"Converts URLs and emails in code to clickable links. Parses Markdown links in comments."},wpd:{title:"WebPlatform Docs",description:'Makes tokens link to WebPlatform.org documentation. The links open in a new tab.'},"custom-class":{title:"Custom Class",description:"This plugin allows you to prefix Prism's default classes (.comment can become .namespace--comment) or replace them with your defined ones (like .editor__comment). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the highlightAll and highlightAllUnder methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},8722:(e,t,n)=>{const r=n(6969),a=n(8380),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(3157).resolve(t)],delete Prism.languages[e],n(3157)(t),o.add(e)}))}i.silent=!1,e.exports=i},9700:()=>{!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,(function(e){if("function"==typeof o&&!o(e))return e;for(var a,l=i.length;-1!==n.code.indexOf(a=t(r,l));)++l;return i[l]=e,a})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(l){for(var s=0;s=o.length);s++){var c=l[s];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[a],d=n.tokenStack[u],p="string"==typeof c?c:c.content,f=t(r,u),m=p.indexOf(f);if(m>-1){++a;var g=p.substring(0,m),h=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),b=p.substring(m+f.length),y=[];g&&y.push.apply(y,i([g])),y.push(h),b&&y.push.apply(y,i([b])),"string"==typeof c?l.splice.apply(l,[s,1].concat(y)):c.content=y}}else c.content&&i(c.content)}return l}(n.tokens)}}}})}(Prism)},8692:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=8692},3157:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=3157},8380:e=>{"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n "));var l={},s=e[r];if(s){function c(t){if(!(t in e))throw new Error(r+" depends on an unknown component "+t);if(!(t in l))for(var i in a(t,o),l[t]=!0,n[t])l[i]=!0}t(s.require,c),t(s.optional,c),t(s.modify,c)}n[r]=l,o.pop()}}return function(e){var t=n[e];return t||(a(e,r),t=n[e]),t}}function a(e){for(var t in e)return!0;return!1}return function(o,i,l){var s=function(e){var t={};for(var n in e){var r=e[n];for(var a in r)if("meta"!=a){var o=r[a];t[a]="string"==typeof o?{title:o}:o}}return t}(o),c=function(e){var n;return function(r){if(r in e)return r;if(!n)for(var a in n={},e){var o=e[a];t(o&&o.alias,(function(t){if(t in n)throw new Error(t+" cannot be alias for both "+a+" and "+n[t]);if(t in e)throw new Error(t+" cannot be alias of "+a+" because it is a component.");n[t]=a}))}return n[r]||r}}(s);i=i.map(c),l=(l||[]).map(c);var u=n(i),d=n(l);i.forEach((function e(n){var r=s[n];t(r&&r.require,(function(t){t in d||(u[t]=!0,e(t))}))}));for(var p,f=r(s),m=u;a(m);){for(var g in p={},m){var h=s[g];t(h&&h.modify,(function(e){e in d&&(p[e]=!0)}))}for(var b in d)if(!(b in u))for(var y in f(b))if(y in u){p[b]=!0;break}for(var v in m=p)u[v]=!0}var w={getIds:function(){var e=[];return w.load((function(t){e.push(t)})),e},load:function(t,n){return function(t,n,r,a){var o=a?a.series:void 0,i=a?a.parallel:e,l={},s={};function c(e){if(e in l)return l[e];s[e]=!0;var a,u=[];for(var d in t(e))d in n&&u.push(d);if(0===u.length)a=r(e);else{var p=i(u.map((function(e){var t=c(e);return delete s[e],t})));o?a=o(p,(function(){return r(e)})):r(e)}return l[e]=a}for(var u in n)c(u);var d=[];for(var p in s)d.push(l[p]);return i(d)}(f,u,t,n)}};return w}}();e.exports=t},2694:(e,t,n)=>{"use strict";var r=n(6925);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},5556:(e,t,n)=>{e.exports=n(2694)()},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},2551:(e,t,n)=>{"use strict";var r=n(6540),a=n(9982);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n