12 Replies Latest reply on Jun 11, 2014 7:57 AM by eric.wittmann

    Thoughts after reviewing Errai Security

    eric.wittmann

      Hey guys - I recently had a look at the documentation for Errai Security being included in Errai 3.0.0.  I have some feedback for you based on the use-cases we have in JBoss Overlord.  But first let me say that what's in the documentation currently makes a lot of sense for many simple use cases (authenticated vs. unauthenticated, simple role-based authorization). 

       

      Single Sign On

       

      The first thing that came to mind when reading the docs was single sign-on.  This is a requirement that we have in Overlord and we currently implement it for all our Errai web apps via a picketlink SAML based IDP/SP approach.  Basically you cannot even get to the host page of our Errai apps without first being authenticated.  The Errai app itself does not have a login form - that's all taken care of via standard web app security stuff.

       

      So this has obvious implications on Errai Security.  I'm thinking the approach to integrate SSO with Errai Security would be to implement an SSO version of a AuthenticationService, correct?  This implementation could simply check the current session to ensure that the user was currently authenticated, and then respond with appropriate information in the current security context.  This will require being able to manifest the current set of roles, which can't be done in an app-container agnostic way.  But it's something that can be done on most (all?) containers.

       

      Fine Grained Authorization

       

      The other looming requirement we have in some of our applications is fine grained authorization.  Roles provide a relatively coarse grained authorization (is the user an "admin" or a "manager"?).  However, we are going to require context sensitive authorization like "does this user have edit permission on entities of type 'foo' for folder 'bar'?"

       

      In the API Management project this is currently implemented using Qualified Permissions which are granted to users via qualified membership in one or more Roles.  In other words, a user might have the following roles (format is Role Name [Qualifier]):

       

      • Application Developer [Red Hat]
      • Application Developer [Apache]
      • Service Developer [Spark Industries]
      • Organization Owner [Foo Inc]

       

      The user has four roles, granting her the following set of permissions:

       

      app_view[Red Hat], app_edit[Red Hat],

      app_view[Apache], app_edit[Apache],

      service_view[Spark Industries], service_edit[Spark Industries],

      app_view[Foo Inc], app_edit[Foo Inc], service_view[Foo Inc], service_edit[Foo Inc]

       

      Notice that the user has the same permission multiple times, but qualified differently.  This means that she is allowed to perform certain actions that require (for example) the "app_edit" permission under certain circumstances (e.g. when modifying applications owned by "Red Hat") but not others.

       

      I don't have a suggestion for you regarding how Errai Security could support this model, or even whether it should.  The difficulty is that when hiding UI functionality (for example) based on the user's permissions also requires domain specific contextual information pull from the state of the current page.  I can imagine a framework that supports this, but I'm not sure whether Errai Security should tackle it.

       

      My assumption is that Errai Security will not support this approach and so instead I will implement my own UI filtering based on current user permissions.  That said, it certainly would be nice to leverage Errai functionality for this.

        • 1. Re: Thoughts after reviewing Errai Security
          hillkorn

          Hi Eric,

           

          we had a similar situation with checking of permissions and we also solved it with an implementation by our own. We created CodeDecorators with an Annotation for a class that marks this class has elements that are bound to permission and this permissions depend to resources like you described. The elements itself are also annotated with the permission as a value.

           

          Our CodeDecorator is parsing all these elements and creates an LifecycleListener that is constructed with an interface as a parameter. The interface provides the resource the permission is bound to. Now when the listener is constructed we add all elements with their permissions to it.

          The listener itself is listening to the StateChange event which is caused if the annotated field with @PageState changes. The filed holds the id the permissions are bound to and now the listener can get it over our interface.

           

          At the end it has the same behavior like the existing StyleBinding but is executed at the time the permissions entity is known.

           

          This solutions works really well as long the navigation is building our widgets. For widgets that are inside others we need to fire the StateChange event by our own.

           

          Greets

          Dennis

          • 2. Re: Thoughts after reviewing Errai Security
            eric.wittmann

            That's really interesting Dennis.  Is the code for this stuff available to view?  I'd love to take a look for inspiration.

             

            My current plan was actually to incorporate this functionality into my application's Page lifecycle.  I have a Page lifecycle that sort of layers on top of Errai's.  So I was thinking that prior to showing the page, but after actually building it, I would visit all of the elements in the page looking for any HTML element with a "data-requires-permission" attribute.  In my case I always know what the qualifier is, so each page will need to provide the current value of the qualifier.

             

            The approach should be easy to implement (will probably just use jQuery) and will handle everything except cases where page content changes after the page is loaded.  But that's something that should happen very rarely.

            • 3. Re: Thoughts after reviewing Errai Security
              hillkorn

              Why do you want to use jQuery? I think it would be an overhead. Because you have to load jQuery and parse all elements that could have a permission if i understand it correctly.

               

              I will try to write a post tomorrow with some code that shows how we solved it. Sorry can't give you a link because the source is not public.

               

              Greets,

              Dennis

              • 4. Re: Thoughts after reviewing Errai Security
                eric.wittmann

                I'm considering using jQuery because my application already uses it (the UI is based on Bootstrap 3).  And it's pretty convenient for jQuery to iterate over all elements which have the 'data-requires-permission' attribute defined.

                 

                Another option would be to visit the elements in GWT, but it's a little bit more code and I doubt the performance difference is very important.

                 

                Looking forward to seeing any code you're willing to share.    Once I have something prototyped in my project I can share a link to it in github.

                • 5. Re: Thoughts after reviewing Errai Security
                  mbarkley

                  Hi Eric,

                   

                  Thanks for the feedback! I have created two new Jiras for investigating SSO integration and finer-grained authorization. I think it would be appropriate for Errai Security to try and address these needs.

                   

                  In the meantime:

                   

                  • I would recommend taking a look at the Errai security cookie for SSO integration. Since you're using PicketLink, you may not need to implement AuthenticationService if you log your user in externally by setting the cookie.
                  • For filtering UI elements, I'd recommend using style bindings. One advantage this approach has is that style bindings are already updated when a user logs in or out or page navigation occurs.

                   

                  Cheers.

                  • 6. Re: Thoughts after reviewing Errai Security
                    eric.wittmann

                    Thanks for the references, Max.

                     

                    The security cookie looks very interesting and I'm sure could be added easily to my app.

                     

                    The style bindings is another feature that I'm sure I could use.  I'll have to think about it a bit more.  One of the nice things about the approach I've described is that I can hide elements without injecting them.  I'm not sure if that's worth going my own way or not...

                    • 7. Re: Re: Thoughts after reviewing Errai Security
                      hillkorn

                      Hi Eric, sorry that this post comes that late.

                       

                      Our Sitiation was that we have a User and a Resource. In our implementation we have our own roles and don't have them integrated to the interfaces that are  provided by java ee and picketlink. But shouldn't make a big difference. So we have a page with many buttons that allow the user to modify the Resource. To modify a resource the user needs a Permission for. So we have a List of ResourcePermissions and every Object in it has the Id of the Resource and the Permission like Modify / Delete ....

                       

                      Now depending on the Resource the user is viewing we want to have the particular buttons activated / deactivated (or hidden if you want).

                      In our case we use the StateChange event to know when the resource id is set. This event is only fired if the page is directly created by the navigation of errai, if this is not the case you'll need to fire it by your own.

                       

                      In the next class you can see our use case we had.

                      @Page(path = EditResource)
                      @Templated("#editResourceContainer")
                      @SecuredPageComponent
                      public class EditResourcePage extends Composite implements HasResource { 
                        @PageState
                        long ResourceId;
                        
                        @Inject
                        @DataField
                        @RequirResourcePermission(ResourcePermissionValue.EDIT)
                        private Button updateButton;
                      
                        ...
                      }
                      

                      The @SecuredPageComponent indicates that this page can have ui elements that are annotated with @RequireResourcePermission.

                      The PageState indicates the field the StateChange event is bound to. The HasResource interface is only an interface with a getter for the id because the StateChange event doesn't provide the value.

                      The updateButton is the ui element we want to secure and should only be available if the user has a EDIT permission.

                       

                      Our @RequireResourcePermission annotation interface looks like this

                      @Retention(RetentionPolicy.RUNTIME)
                      @Target({
                          ElementType.FIELD
                      })
                      public @interface RequireResourcePermission {
                       public ResourcePermissionValue value();
                      }
                      

                      This annotation has nothing special it only allows us to mark a ui element with a permission.

                       

                      Here the @SecuredPageComponent

                      @Retention(RetentionPolicy.RUNTIME)
                      @Target(ElementType.TYPE)
                      public @interface SecuredPageComponent {}
                      

                       

                      Now the more important part how it's done.

                      We have a LifeCycleListener for the StateChange event that checks the permissions and disables (hides) the elements.

                      public class SecuredPageComponentLifecycleListener<W extends IsWidget> implements LifecycleListener<W> {
                        private final List<Tuple<Element, RequireResourcePermission>> elementsWithPermissions = new ArrayList<Tuple<Element, RequireResourcePermission>>();
                        private final HasResource resource;
                        private final AuthorizationUtil authorizationUtil;
                      
                        public SecuredPageComponentLifecycleListener(HasResource resource) {
                          this.resource = resource;
                          authorizationUtil = IOC.getBeanManager().lookupBean(AuthorizationUtil.class).getInstance();
                        }
                      
                        public void addElementPermission(Element element, RequireResourcePermission resourcePermission) {
                          elementsWithPermissions.add(Tuple.of(element, resourcePermission));
                        }
                      
                        @Override
                        public void observeEvent(LifecycleEvent<W> event) {
                          for (Tuple<Element, RequireResourcePermission> elementPermissionEntry : elementsWithPermissions) {
                            final ResourcePermissionValue resourcePermissionValue = elementPermissionEntry.getValue().value();
                            final Element element = elementPermissionEntry.getKey();
                            if (!authorizationUtil.validateResourceAccess(resourcePermissionValue, resource.getResource()))) {
                              element.addClassName(RequireSystemPermissionOnElementHandler.DISABLED_CLASS_NAME);
                            }
                          }
                        }
                      
                        @Override
                        public boolean isObserveableEventType(Class<? extends LifecycleEvent<W>> eventType) {
                          return eventType.equals(StateChange.class);
                        }
                      }
                      

                      The SecuredPageComponentLifecycleListener is initialized with the Page to get the ResourceId by the HasResource interface when the StateChange is observed.

                      After the Listener is initialized all annotated buttons will be added by the addElementPermission method. How this is done is described later.

                      Then on StateChange we iterate over all elements that were added and check their permission. We use a AuthorizationUtil to check the users permission but this part is different for the most i think. The important point for the AuthorizationUtil is that is provides a method to check the users permission against. Our AuthorizationUtil accepts the permission value and the resource id to find out if the user is allowed to have the element. And if the user is not permitted we add a css class that disables the ui element.

                       

                      The next class will be the most important. Our CodeDecorator that initializes the Listener and adds the elements for all classes with a SecuredPageComponent annotation.

                      @CodeDecorator
                      public class SecuredPageComponentCodeDecorator extends IOCDecoratorExtension<SecuredPageComponent> {
                      
                      
                        public SecuredPageComponentCodeDecorator(Class<SecuredPageComponent> decoratesWith) {
                          super(decoratesWith);
                        }
                      
                      
                        @Override
                        public List<? extends Statement> generateDecorator(InjectableInstance<SecuredPageComponent> ctx) {
                          final List<Statement> stmts = new ArrayList<>();
                          MetaClass enclosingType = ctx.getEnclosingType();
                      
                      
                          if (!enclosingType.isAssignableTo(HasResource.class)) {
                            throw new RuntimeException("Found an error at " + enclosingType.getCanonicalName()
                                + ". SecuredPageComponent is only allowed on classes that implements the interface: "
                                + HasResource.class.getCanonicalName());
                          }
                      
                          List<MetaField> securedFields = enclosingType.getFieldsAnnotatedWith(RequireResourcePermission.class);
                          if (securedFields.size() > 0) {
                            registerSecuredPageComponentLifecycleListener(ctx, securedFields);
                          }
                      
                          return stmts;
                        }
                      
                      
                        private void registerSecuredPageComponentLifecycleListener(InjectableInstance<SecuredPageComponent> ctx,
                            List<MetaField> metaFields) {
                          final String securedPageComponentListenerVar = ctx.getInjector().getInstanceVarName()
                              + "_securedPageComponentListener";
                      
                          Statement[] initStatements = new Statement[metaFields.size() + 1];
                          Statement valueAccessor;
                          Statement element;
                          // Register all secured fields at the listener
                          for (int i = 0; i < metaFields.size(); i++) {
                            RequireResourcePermission permission = (RequireResourcePermission) metaFields.get(i).getAnnotation(
                                RequireResourcePermission.class);
                      if (metaFields.get(i).getType().isAssignableTo(Element.class)) {
                              // field is already an element
                               element = InjectUtil.getPublicOrPrivateFieldValue(ctx.getInjectionContext(), Refs.get(ctx.getInjector().getInstanceVarName()), metaFields.get(i));
                             } else {
                               valueAccessor = InjectUtil.getPublicOrPrivateFieldValue(ctx.getInjectionContext(),
                                   Refs.get(ctx.getInjector().getInstanceVarName()), metaFields.get(i));
                               // get element that should be secured
                               element = Stmt.nestedCall(valueAccessor).invoke("getElement");
                             }
                            // Register statement
                            initStatements[i] = Stmt.loadVariable(Refs.get(securedPageComponentListenerVar)).invoke("addElementPermission",
                                element, permission);
                          }
                          initStatements[metaFields.size()] = Stmt.invokeStatic(
                              IOC.class,
                              "registerLifecycleListener",
                              Refs.get(ctx.getInjector().getInstanceVarName()),
                              Refs.get(securedPageComponentListenerVar));
                      
                          ctx.getTargetInjector().addStatementToEndOfInjector(
                              Stmt.declareFinalVariable(
                                  securedPageComponentListenerVar,
                                  MetaClassFactory.parameterizedAs(SecuredPageComponentLifecycleListener.class,
                                      MetaClassFactory.typeParametersOf(ctx.getInjector().getInjectedType())),
                                  Stmt.newObject(SecuredPageComponentLifecycleListener.class,
                                      Refs.get(ctx.getInjector().getInstanceVarName()))));
                          ctx.getTargetInjector().addStatementToEndOfInjector(
                              Stmt.loadVariable("context")
                                .invoke("addInitializationCallback",
                                    Refs.get(ctx.getInjector().getInstanceVarName()),
                                    createInitializationCallback(
                                        ctx,
                                        initStatements)));
                          ctx.getTargetInjector().addStatementToEndOfInjector(
                              Stmt.loadVariable("context")
                                .invoke("addDestructionCallback",
                                    Refs.get(ctx.getInjector().getInstanceVarName()),
                                    createDestructionCallback(
                                        ctx,
                                        Stmt.invokeStatic(
                                            IOC.class,
                                            "unregisterLifecycleListener",
                                            Refs.get(ctx.getInjector().getInstanceVarName()),
                                            Refs.get(securedPageComponentListenerVar)))));
                        }
                      
                      
                        private Statement createInitializationCallback(final InjectableInstance<SecuredPageComponent> ctx,
                            final Statement... statements) {
                          return createCallback(InitializationCallback.class, "init", ctx, statements);
                        }
                      
                      
                        private Statement createDestructionCallback(final InjectableInstance<SecuredPageComponent> ctx,
                            final Statement... statements) {
                          return createCallback(DestructionCallback.class, "destroy", ctx, statements);
                        }
                      
                      
                        /**
                         * Creates a callback inside a statement that executes all given statements.
                         * @param callbackType
                         * @param methodName name of the method that should be overwritten for the interface
                         * @param ctx
                         * @param statements that are executed in the callback
                         * @return callback creation statement
                         */
                        private Statement createCallback(final Class<?> callbackType, final String methodName,
                            final InjectableInstance<SecuredPageComponent> ctx, final Statement... statements) {
                          BlockBuilder<AnonymousClassStructureBuilder> callbackMethod = Stmt.newObject(
                              MetaClassFactory.parameterizedAs(callbackType,
                                  MetaClassFactory.typeParametersOf(ctx.getInjector().getInjectedType())))
                            .extend()
                            .publicOverridesMethod(
                                methodName,
                                Parameter.finalOf(ctx.getInjector().getInjectedType(), "obj"));
                      
                      
                          for (int i = 0; i < statements.length; i++) {
                            callbackMethod = callbackMethod.append(statements[i]);
                          }
                      
                          return callbackMethod.finish().finish();
                        }
                      }
                      
                      
                        /**
                         * Creates a callback inside a statement that executes all given statements.
                         * @param callbackType
                         * @param methodName name of the method that should be overwritten for the interface
                         * @param ctx
                         * @param statements that are executed in the callback
                         * @return callback creation statement
                         */
                        private Statement createCallback(final Class<?> callbackType, final String methodName,
                            final InjectableInstance<SecuredPageComponent> ctx, final Statement... statements) {
                          BlockBuilder<AnonymousClassStructureBuilder> callbackMethod = Stmt.newObject(
                              MetaClassFactory.parameterizedAs(callbackType,
                                  MetaClassFactory.typeParametersOf(ctx.getInjector().getInjectedType())))
                            .extend()
                            .publicOverridesMethod(
                                methodName,
                                Parameter.finalOf(ctx.getInjector().getInjectedType(), "obj"));
                      
                      
                          for (int i = 0; i < statements.length; i++) {
                            callbackMethod = callbackMethod.append(statements[i]);
                          }
                      
                          return callbackMethod.finish().finish();
                        }
                      }
                      
                      

                      This class needs to be outside of your gwt compile path.

                      This CodeDecorator is created and called for all SecuredPageComponent annotated classes. It checks that the class implements the HasResource and if not it throws an exception.

                      It will create the SecuredPageComponentLifecycleListener and gives the annotated class as a parameter on construct.

                      After the creation of the SecuredPageComponentLifecycleListener it will add all elements with their permissions to the listener.

                      And it register and unregisters the listener on init and descruct. This CodeDecorator is in many things really similar to the errai/errai-security/errai-security-client/src/main/java/org/jboss/errai/security/rebind/PageSecurityCodeDecorator.java …

                      But able to check against resource bound permission.

                       

                      If you have any more question post them and i will try to answer them as fast as possible

                       

                      PS: To secure whole pages this way we have another CodeDecorator that works in the same way the one in the errai-security does but with the HasResource interface and StateChange event and another listener. If you'll interested in the code i can also write it down here.

                       

                      Regards,

                      Dennis

                      • 8. Re: Re: Thoughts after reviewing Errai Security
                        eric.wittmann

                        This was really great, Dennis.  Thanks for sharing!

                         

                        You are right - this is very similar to my use-case.  We both are using qualified permissions to secure pages or elements within a page.  Yours is a very nice approach.

                         

                        I would say that your approach has the benefit that it is not tied in any way to the templating feature of Errai, which is nice.  On the other hand that means you need to inject any element that you want to hide/disable.  That's only a small disadvantage, since it's unlikely you'll need to secure some non-functional piece of the UI.

                         

                        Out of curiosity, where do you get the current user's permission set from?  Do you pull it in as part of the host page or via some separate rpc call?

                        • 9. Re: Thoughts after reviewing Errai Security
                          hillkorn

                          Yes we request and cache them with an HTTP call for 1 minute. Not the best solution but i think we will change it if we need that.

                           

                          But if you have something like this

                          @RequirResourcePermission(ResourcePermissionValue.EDIT) 

                          private Element updateButton = DOM.createDiv();


                          It should work i think. Because it's already created on initialization.

                          But i think you mean after the SateChange event is fired and you'll add/replace some elements that need a check. Then yes it will not check them, also not if a StateChange is fired again.

                          • 10. Re: Thoughts after reviewing Errai Security
                            eric.wittmann

                            Keeping them for 1 minute seems more than reasonable to me.  I often simply keep them for the full lifetime of the UI session, since they aren't expected to change often.  And if they do, a simple refresh of the page will suffice.  But as you say, it's something easily changed if necessary.

                             

                            Regarding the DOM.createDiv() example you mention - that's another nice result of your approach - you can lock down anything that's a field in the page.  What I meant was that you couldn't lock down something in a template without also injecting it into the page.

                             

                            I actually *wasn't* thinking about how to lock down UI elements if they are created after the page lifecycle completes (i.e. after the StateChange event was fired and handled).  But that's a good point - if you have a table of data that is dynamically updated without navigating to another page (or the same page) then you'll need to trigger the StateChange event manually or handle the security in some other way.  I don't really see a way for a framework to do that for you automatically though.  The progreammer can always add more UI elements to the page *after* the page lifecycle is complete.  Either via a timer or as a result of the user clicking on something, etc.  It's something to be aware of though I suppose.

                            • 11. Re: Thoughts after reviewing Errai Security
                              mbarkley

                              Eric,

                               

                              I've now resolved the Jira issue for finer-grained authorization in Errai Security, having pushed a new Errai Security feature to 3.1.0-SNAPSHOT. Here is a quick description of how the new feature works:

                               

                              You can now add providers of required roles to a RestrictedAccess annotation. These providers are passed in as an array of types, which are instantiated as needed for security access checks. These providers can return any type that implements the Role interface. Roles from the resource will be compared against roles from the user with the equals method. My hope is that it should be simple enough to create a Role implementation that would allow more complex roles like the qualified roles you described.

                               

                              Here is a quick example of a provider and its usage in a RestrictedAccess annotation:

                               

                              Here is the provider.

                               

                              @Dependent
                              public class AdminRoleProvider implements RequiredRolesProvider {
                              
                                   public Set<Role> getRoles() {
                                        return new HashSet(Arrays.asList(
                                             new RoleImpl("admin")
                                        ));
                                   }
                              }
                              

                               

                              And here is what the usage in the annotation looks like.

                               

                              @RestrictedAccess(providers = { AdminRoleProvider.class })
                              

                               

                              I will publish documentation for this as soon as I get a chance. If you have the time, I would appreciate your thoughts on this. Do you think this would meet your needs?

                               

                              Cheers.

                              • 12. Re: Thoughts after reviewing Errai Security
                                eric.wittmann

                                That looks like a solid approach.  I look forward to giving it a try!