 Thanks for joining me here. So I'm going to talk about advanced permissions system using the groups module. Assuming everybody here did something use the groups module or at least the OG module in Drupal 7, it was a very similar thing, but basically just wrapping that, I mean, having that connection between various different entities and doing then some stuff with it. So a couple of things about me. My name's Jan. I'm a full stack developer at NDP. You can see all the handles where you can reach me for some reason or you could just grab me outside if needed. If you're going to have any more questions about this, if anything wasn't explained as well as you might have think, just like I said, grab me outside. So the idea here is I'm going to give you some background. We're going to go through some theory and then we're going to switch to the code base because there's quite a lot of code base and I promised some code examples, which I think is going to be the best way to show you how things are working, how things have been done. So some background first. This was actually done for a client of ours. So we sort of had this need where there were a lot of users on the system and these users created various different pieces of content. There were some notes, there were some media files and a lot of other custom entities that we also built. Now that itself is fine but then we also had a lot of top level users roles which had different permissions based on what kind of role they have. And also it was also depending on the group types. So we had a bunch of different group types in which the users were and into which we put these top level roles and then based on the permissions that we set in the groups, we had to determine what kind of permission, what kind of access those users would actually have to those entities that the users created. So to wrap it up, we've got the users on one hand and the users create all these different types of entities and then above them we've got different group types with different roles and permissions inside of the groups. And we also have these top level users, so things like supervisors, editors, a bunch of them. And all of that sort of needs to work together. It needs to give you the proper permission when you need it. So here's an attempt to visualize how the whole thing actually looks like. So like I said, we've got the groups in the middle which are actually the main thing of the whole story that we have here. And the groups connect everything from the entities that the user can create all the way up to the supervisors that can view these entities. So it's always going to be the entities that's going to be the thing that somebody is going to want to see or do something on it. And I'm also showing that you could have an editor above inside of the group and then a supervisor. And you could also see that one supervisor could be in two different groups. So it's like a mixture of all of these things together. So you're probably going to say that groups has all of that built in. And you're not wrong. Groups has all of this pretty much accessible for us. So you can put content inside of groups. You can set different permissions. You can set different roles inside of the groups. But the thing was that we expected quite a lot of content to be here. So we're talking about tens, hundreds of thousands of users, entities, things like that. What that means is groups has this built in. But you need to have the group content. So if you're familiar with groups, you've got the group entity. And then inside of that, you've got the group content entity, which is the actual entity that connects these two together. So when you add, let's say, a user into a group, that creates a group content. And it references both of these things together. So the group into which you're putting that user and the user, which is actually the one you're putting the group into. So why we have an issue with this is, like I said, we've got a lot of content. And basically just thinking about it, it seemed a bit scary to have all that amount of content, that amount of entities being created in the back end. Not just creation of them, but seeing this is like a permission system. It means we're also going to have to query all of them. So eventually we decided that we needed a more custom approach to it, just simply because the group content, the amount of those entities that might be created and that might be queried, just seemed a bit too scary. So we went down this path to create this custom build permission system. So we've got a couple of different things that we needed to think about. So first of all, we've got different entity types. We somehow need to put all those entity types into groups. By default, groups have, I think, notes and users, maybe something else that you can enable. And then you can add those types of entities into your groups. But that's about it. We, on the other hand, had a need for a lot more of these things. We also had custom entity types. So that means we also need to put those inside of the groups. Now, when I say put inside of the groups, it's not the way you might think. It's not the group content that I was explaining about before. But we'll get to that. I'm going to explain exactly what I mean by that. So the next thing was obviously entity-level access. So when somebody wants to view, edit, delete, whatever, to the entity, that sort of permission system needs to kick in and say, yeah, so we've got this user which belongs in this group. It has these sort of permissions. It says that he can view that, so let's get him that. The second bit was the list-level access. Now, yeah, we did have, we always rendered entities out. So just by rendering the entities out, the entity-level access sort of kicked in, which means if you didn't have access to a specific entity, you didn't see it. But the problem arose when, so we used Search API to list and filter all of these entities. Now, the problem was that, yes, you didn't actually see the entity that you weren't allowed to see. But also, when using facets, or for example, the, what's it called, in the header, the summary of how many results do you see, that wasn't actually accurate. Because even though that, let's say, you have 100 entities being shown to you, the summary is going to say, you've got 100 entities over there. But because you don't have access to some of them, it's still going to say 100. But the actual entity is just not going to get rendered. So we also needed to sort of fix that. And what we did is we had to basically go into the query itself. So once you do the whole search, once you do the whole query, you needed to alter that in a way that you actually got back just the entities on which you were able to see to do some sort of stuff on it. Then we've got the special cases. Obviously, it's never going to go as easy as you might think. So I'm just going to do the entity level, the list level, all of that. I'm going to click all of that, things like that. And eventually, somebody's going to come in and say, well, for this particular case, yeah, we need something a bit different than what it is. And first thing came in, we did it into the system. Second thing, third, fourth, and so on, we started to see something that we needed to address. So we sort of decided that we needed to have a special system just in place to handle all of these special cases. And with that, also enable other modules to plug into that and provide their own special cases for access. And then, obviously, managing all components, this basically just means that there is a service, like a manager, that is going to pull all of these things together, do all of the logic in the back end, and basically just have one point of, let's say, one point of contact into which you just need to look at or do things. And basically, that file, that manager itself is the one responsible for actually giving the access and sort of joining all of these components together. So, as I said, we need to allow new entities in groups. There's a nice plugin provided by groups itself. It's called Group Content Neighbor Plugin. So those of you familiar with plugins, they're very easy to use. You basically just take the file that it is, create your own one. There are some classes that you need to extend. There are some annotations that you need to add. But essentially, that provides you with a nice way for which that you can use to add your own entities inside. And with that in mind, we also needed to build the system in a way that's gonna be easy for other developers when sort of building their own entities or say that they have an entity already. The which they wanna put into the whole system permission. They needed to have a tool or a system to easily do that and just make sure that they do that without any issues. They do that without any problems. They do that without breaking the system. So that's something that was also kept in mind. Then on, like I said, the entity level access, we've got view, update, delete actions. I think some other ones got into place so things like view, own, published, view, own and published. It's basically up to you to determine which ones do you wanna put inside. But that also meant that we had to override the existing access control handlers. So those familiar with entities, there's this big annotation text on top of the class and inside of which there is one particular one which is interesting for us is the access control handler. So that defines the class that determines the access to that entity. So anytime, for example, let's say nodes, when you try and open up a node, there is an access control handler class that's being triggered in the back that determines if you do have access to that node. Then this level access, as I said, the main thing was here was that the queries needed to be altered. So we didn't want that facets, the summary of all those things and the search API to provide you with wrong data. And then again, we also think there's a lot of these entities you don't wanna be putting out all of them if it's not needed, right? So you might have 10, you might have 100, you might have 10,000 of them. If there's a way that you can do something to not put that much of a performance issue on the site, I mean, you should do it if you ask me. And yeah, the special cases, like I said, we've seen, we got back a couple of times that there are some special cases that need to be implemented and then eventually we decided that we needed the system that's gonna handle all of these special cases. So what we did is we actually built a custom plugin manager and type. I'm gonna show a bit more into detail. I'm not gonna go exactly into detail on the whole manager, I mean, on the whole plugin manager and type thing, just sort of like top level things you can see. But essentially, yeah, the whole plugin system, the whole plugin API was actually very convenient for us because we could, we were able to build these types and then what that meant is we could basically just create new plugins. Other modules would create these plugins and we would sort of just build that small bit of logic into our system that would sort of check all of these plugins and then determine what the access was. If there are some special cases, for example. And the service manager, as I said, this is basically just to wrap things up, to have them all in one place. I guess it's a matter of developer to developer on what they wanna do or how they wanna do these things. But the way I see it, it's good to have like this service manager that's gonna handle you all the things, I mean, that it's not the same as a plugin, that it's not like a controller, things like that. You put it in a service, all of those helper methods that you might use throughout the system and things like that. So now I'm gonna go into the whole code, the whole code base. At this point, we might have, I mean, we're probably gonna have a lot of code to show, so if anybody feels that, I mean, they have a question or something that's not exactly clear, you could just raise your hands and interrupt me at that point and I can give you more detail on the whole thing. How visible is this? It is, isn't it? Is it possible to turn off the controls? Lights. No. No, no, no, no. Yeah. Oh. Yeah. Is there a power point? All right. I think we can just, I think this is probably the best, right? Yeah. Is that okay, everyone? Yeah. Yeah, it's good. Yeah, that's okay. We're gonna get an object. Let me know, I can put the mic on again. Is that good? Who said that? Yeah. Let's leave it at this, and at least we see what the code is. All right, so I said that the first thing is to have all of these entities, all of these constant entities to be shown inside of the group. So why we need that? It's not because we wanna actually reference the group inside of that, but the group content neighbor also provides us with a nice way to define different permissions for that specific entity type. So with that, in theory, I mean, very shortly, what it meant is I have a custom entity, or in this case, I've got file, media, user, network submissions. I wanted to put these entities into the groups, and then once I did that, I was able to define different permissions for different roles. So for example, the editor might have access to a file, whereas the supervisor might have access to a web from submission. So once these group content and neighbor plugins were built in, we were able to quickly add all of these content types. Sorry, are you able to zoom in your code at all? Let's see. Do you mean you may be able to just... Does anyone know where to do that? It might be on the view to run... Control plus, that one. Command plus, Command plus. Command plus doesn't work, I just tried it. It's a case piece to it. You can probably use like this. What's the code style for the demo? Four. Probably not six. Oh. Oh. It was 12. You can use your trackpad to... You can? Yes, I just did it. Oh, you can. Cool. Thank you. So let's do this. It's better? Yes, much better. All right. So as we said, this is a plugin. You're familiar with plugins. The top thing, the text is the annotation which sort of defines the whole thing. It's basically just taken from whatever the group module is providing us with. But it has, you might see that it extends different things than what the module, the contribute module might provide us. So that is tracking back to the thing that I said that we need to provide like a better system for other developers to put these files in. So that we have like this uniform system of all the files and how all the entities are being added in. So you can see that this here, we only have two methods. So get bundle type ID and get entity type ID. I'm gonna go through the other classes and it's gonna make much more sense. So for example, let's go into the base one. So like so. You can see that the base one actually extends the base. So that is the group content, the neighbor base. So this is the one that is provided and used by the contribute module. Why we do this is basically just we need some sort of additional things in the class that it does for us. And it's just a better way to manage your code base like this. So it's always gonna be this one class that's gonna be providing all the methods that your own plugins are gonna be using. Looking down, there's nothing specific, maybe some different permissions, but other than that, the methods being used here are all the ones that the base method, the base class from the contribute module provides. I think we just changed a couple of permissions, maybe added things like view, own, publish permission. So this is where you can do this. So for example, you've got the permissions, the group operations, you've got them defined here. So if you want different things in the site or different permissions, you could write your own stuff in here. And then because it's a base class, because all the other classes are gonna use this, all the entities that you're gonna add into the group are gonna be using this and are gonna be defining these sort of permissions. So again, like I said, just a more standard-less way for other developers to add these permissions inside. And if I go back, so we've also got the interface and the interface, as you know, just defines like this blueprint on how one class should be defined and which methods should it use. So in this case, it's basically just get bundle type ID and get entity type ID. So once you have your own plugin, you need to define these, you need to return the actual names of the bundle and of the entity type and the base class is gonna do all the rest of the stuff for you. I can quickly, this always resets back. So we've got the get entity type bundle, you can see it here. Which means that any sort of class, any sort of plugin that we have has to define those methods and then all of these methods are being used in the base class to define your entity types. So going back, you can see that this shows up, okay. Any sort of entities that we wanna have are defined like this. All of them implement the interface, which means that the only thing you actually need to do is pretty much copy the file and define the bundle and entity types. And clear the cache, set up the IDs in the notation and you're good to go. After which, you go into your group definition and you will be able to see this entity type inside of it. So that was the first thing that we needed to have. So sort of get all of those entities inside. Now the second bit was obviously the access, the entity access, so entity level access. And with that, we also needed to override the existing node access control handlers. So like I said, what that is, is it's a class that each entity has and it defines these permissions. It does stuff in the backend for you. Couple of things that you can then define and on which the system's gonna act on. So in this case, you can see that this one extended the existing node access control handler and this was the case with all those. So any sort of entity that we wanted to put inside needed to have a access control handler. It always extended the existing one and then it just defined its own permission. So this here, what you see is the very basic what you need to do. So you can see we've got the access manager here. It's applicable method for the entity and what this does is it basically just gives you a true or false if the entity is actually applicable for this sort of permissions. And if it is, it goes again into the access manager and it checks access. You can see that it's basically very much the same as the, so here you can see the parent one. And if for example, whatever reason it's not applicable, it still defaults back to the parent one. So pretty much nothing breaks. We'll go into the access manager a bit later on but essentially this is how the access manager, sorry, the access control handler looks like. Now just having this is not enough. We also need to override it. So override the existing one on the entity and you can easily do that with the hook entity type author. So again, here we've got an array of all the entity types and we call the access manager and then we call the method in the access manager which is written by us that it sort of loops over all the entities that we have and it sets a new handler class. So you can see it like this, set handler class. This is the handler class that we wanna override and this is our new class that I was showing you before. So with this, you create a cache and the new classes to handle access on the entity is being used. So the second bit I was showing you was the list level access. So with this, we had to build another, not build but use another plugin which is a plugin that's used by Search API. So it's called a Search API processor. You might see it if you go into your search index and then under I think processors you've got this list of check boxes and you basically just select which one you wanna have and with this, we added a new processor into that and basically what it just does is it sort of injects itself into the whole logic of the Search API and then you can do various stuff with it. So I'm gonna show you what exactly was there. There's a lot of code here which was pretty much just taken from the existing core one. Is this still visible in the back? So the main thing here is, I mean, like I said, there's a lot of methods here which needed to be there so to sort of define what's going on, to define on which entity this is gonna go on but the main bit that I wanna show you is the function called add group access. So in here, what we do, so like here, this is basically the main logic from here on that it starts. So the logic here is the following. We have a user that is trying to view a list of entities. So what we do is we get that user's groups. So all the groups that the user is in that could be one, it could be five, it could be 500, it doesn't matter. So on all of the entities that he is in, we then check the group content enabler plugin which means that we know what kind of entities the search can act on. That's all based on the group that the user is in. And then we also check the permissions inside of those groups. So for each entity that we have defined in the group content enabler, we've got things like view, we've got things like edit in the lead. And based on that, based on the entities, based on the user, the groups, we define which groups are actually good to show. Now there's one more bit that goes in the back end which is when doing the index, so any sort of entity that's being indexed has this group identifier on it. So let's say the user creates a content and based on that user, so on the answer of the user, it takes the group that I'm in and it takes that ID and indexes it with together with that node. So together with all of that, so the ID of the group and then the IDs of the groups of the user that's trying to view that, we sort of combine those together and we make like an intersection on which groups are good to show. So it's essentially at the end, we have, so it goes like this, the main bit is this line here. So what it does is it injects itself into the query and basically just adds a list of group IDs on group IDs from the entities that it can show. So what that does is basically just, it removes all the entities that shouldn't be shown to that user. So for example, if I'm the user that wants to see a list of entities, I would just get the list of entities that I'm available to see. So the list of entities that are in my groups and that I have access to. Any questions here? Just that, so that happens in the query itself. So you don't pull it back from, say, solar and then restrict it after the query. No, that's before. So during the build of the query, this gets injected into the query. So before all of the data's being pulled out of the index. Yeah. Okay, so we've got entity-level access, we've got list-level access. Now we've got the special cases. So like I said, with the special cases, we've built our own plugin. I don't wanna go too much into the whole plugin API because that's gonna take us probably another two hours. But let's see what good one would be. Let's say reference supervisor. And so again, we've got the whole annotation that we have here. So these are the things that we've defined in the plugin manager. So things like which entity type ID, what are the excluded entity type IDs, what is the type, so the type is allowed or forbidden. So what we've done here is add the ability for other developers. Yeah, the ability for other developers to sort of inject themselves into the whole logic and just define what different special cases they might have. So for example, this specific one is called reference supervisor. What this is is that the entities could have a field on which the user would select their own supervisor. So I would create an entity, let's say user B would be my supervisor. I add him into the entity, and that itself, because of this plugin, will grant him access to that content. Now depending on the whole structure of the annotation, it could also prevent him to access that entity. So you can see that type is allowed, type would also be forbidden. That's the way we defined it. And with this, we got this nice quick system of sort of providing other modules and other developers to inject themselves into the whole logic and determine what kind of access they want. I mean, if there is a special case for it. Now the other thing I wanna show you here is, so the plugin itself, that's just one bit. Now I wanna show you the main bit, which is the access manager. So we've got that here, all right. And so like I said, this is the manager that sort of handles all those components that we've seen together. I can show you, so for example, we've got the constant handlers. If you remember in the module file where we did the hook entity, something alter, these are the handlers that are defined. So these are the entities which need to have a new access control handler defined. So in the module file, we call this, we loop over these and we can see that this is the entity type and this is the new class that we need to have. Now I wanted to show you the whole plugin thing. So that would be this method. So we've got a method called check special cases and this method is called whenever we need to check access to a specific entity. So a user goes, tries to view the entity. This is the one that fires before all of that. So I mean, there wouldn't be any point in having these special cases if they didn't take priority over the normal logic. So very quickly going over this, we've got the special case manager. So any sort of plugin you create is gonna have a manager. And with that, we use this to load up all of the special case plugins and then we just loop over the special case plugins. So these are the bugging things. So that would be, yeah, so it's basically just like that. So any sort of, any plugin that we have has to have the check access method on which the logic is being done. So any developer that does that plugin is gonna create it, it's gonna create the annotation. It also needs to create this method and inside of that method is gonna be your special case logic. So with this, what you see right here is it loads up all of the special cases based on the entity, you can see it here. So it gets the entity type ID, also the type. So the type is forbidden or allowed. And it's gonna get you all the special cases that apply for this particular case of yours. And then it's going to loop over all of them. The manager is gonna create an instance. So this basically just creates a working class for you, all of the plugin. And because of that, you can then call the check access on that plugin. Now, depending on what you wrote in that logic, it's gonna return a true or false. It also depends on the type. So if it's allowed or if it's forbidden, but essentially all of those things together will define your special case where you wanna grant or disable access for a specific case. Any questions here? All clear? Probably not. So that's essentially it. It was a very quick presentation of the whole thing. Because there's, yeah, there is a lot of moving parts, but hopefully it will just give you some sort of idea on what's behind that, maybe help you in your future projects, maybe give you an idea on how you might tackle a specific case. But yeah, essentially that was the case. We had fun things working with. Especially with the Search API. Yeah, we got it to the end. Thank you. Do you have a question? Yeah. The module that you wrote, is that very specific to your customer requirements? Or is that something that is generic enough to be a contract? I try to build every module to be generic enough. I would say that there are some cases that are more specific to the project. But I think with very small modifications, you could take this module and put it elsewhere. Do you need it? That's a handy thing. Not yet. Yeah? It sounds like the kind of thing that, yeah, probably would be in a common piece, that kind of finer-grained access to control. Yeah. For all the things, I agree there's, you know, specific requirements that you kind of take into the code that are based on assumptions, opinions. Yeah, I mean, even though the module that you've seen is actually more like a shrink-down version, because there are other entity types, there are other plugins that we use, but I just wanted to minimize all of that, because, I mean, this itself, I would say, was quite overwhelming as it is. The whole thing is even more so. How's the other question here? It's the same. Yeah, sorry, right? Yeah. I should say I work with God, and I worked on a little bit of work on this project. And just to give people contact, or some context for it, it's continuing professional development and training for a Royal College in the medical field. And I think Lisa Proctor was saying, starting from scratch, we've now got like over 200,000 entities in it, created so far. Could be. Yeah. And it's, you think the code is complex, but the organization structure is just incredibly Baroque and Byzantine, and as John says, has tons of special cases, which made all this necessary. Yeah, so I mean, like you said, the whole complexity of the project really requires to think this through and to make it easy for any sort of future changes or any sort of future modifications or additions to the system to make it easy. I mean, we've had several cases where the client came back and said, so we've got this and this and that. I mean, it's another thing that needs to be done. And because of the whole structure, because of the whole thought process that happens at the start, those changes were actually then very quickly and very easily done. Yeah, I think we're up, so. Thanks.