You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2021/04/19 14:42:34 UTC

[myfaces-tobago] branch master updated: fix(AuthorizationHelper): RolesAllowed annotation for composite-components

This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


The following commit(s) were added to refs/heads/master by this push:
     new f94a0a5  fix(AuthorizationHelper): RolesAllowed annotation for composite-components
f94a0a5 is described below

commit f94a0a5b2cdca0c871df67b7fb44b1928d029a10
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Mon Apr 12 13:50:55 2021 +0200

    fix(AuthorizationHelper): RolesAllowed annotation for composite-components
    
    * fix AuthorizationHelper if a controller is set as cc attribute
    * example added
    * jasmine test added
    
    Issue: TOBAGO-2008
---
 .../internal/component/AbstractUICommandBase.java  |  6 +-
 .../tobago/internal/util/AuthorizationHelper.java  | 72 +++++++++++++++++++---
 .../tobago/example/demo/RoleController.java        | 12 ++++
 .../2008-roles-allowed/Roles_Allowed.test.js       | 71 +++++++++++++++++++++
 .../2008-roles-allowed/Roles_Allowed.xhtml         | 47 ++++++++++++++
 .../resources/demo-components/rolesTest.xhtml      | 31 ++++++++++
 6 files changed, 225 insertions(+), 14 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUICommandBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUICommandBase.java
index 7321b2e..bb7d1f7 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUICommandBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUICommandBase.java
@@ -125,11 +125,7 @@ public abstract class AbstractUICommandBase extends UICommand
     final AuthorizationHelper authorizationHelper = AuthorizationHelper.getInstance(facesContext);
     final MethodExpression actionExpression = getActionExpression();
     if (actionExpression != null) {
-      final boolean authorized =
-          authorizationHelper.isAuthorized(facesContext, actionExpression.getExpressionString());
-      if (!authorized) {
-        return false;
-      }
+      return authorizationHelper.isAuthorized(facesContext, this, actionExpression.getExpressionString());
     }
     return true;
   }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/AuthorizationHelper.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/AuthorizationHelper.java
index 87a3318..7d488fc 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/AuthorizationHelper.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/AuthorizationHelper.java
@@ -28,6 +28,8 @@ import javax.annotation.security.RolesAllowed;
 import javax.el.ELContext;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.inject.Named;
 import java.lang.annotation.Annotation;
@@ -52,6 +54,8 @@ public class AuthorizationHelper {
 
   private static final Pattern PATTERN = Pattern.compile("#\\{(\\w+(?:\\.\\w+)*)\\.(\\w+)(?:\\(.*\\))?}");
 
+  private static final String CC_ATTRS = "cc.attrs.";
+
   private static final Annotation NULL_VALUE = new Annotation() {
     @Override
     public Class<? extends Annotation> annotationType() {
@@ -72,9 +76,9 @@ public class AuthorizationHelper {
         elContext.getELResolver().getValue(elContext, null, AUTHORIZATION_HELPER);
   }
 
-  public boolean isAuthorized(final FacesContext facesContext, final String expression) {
+  public boolean isAuthorized(final FacesContext facesContext, final UIComponent component, final String expression) {
 
-    final Annotation securityAnnotation = getSecurityAnnotation(facesContext, expression);
+    final Annotation securityAnnotation = getSecurityAnnotation(facesContext, component, expression);
     if (securityAnnotation == null) {
       return true;
     }
@@ -107,23 +111,22 @@ public class AuthorizationHelper {
     return true;
   }
 
-  private Annotation getSecurityAnnotation(final FacesContext facesContext, final String expression) {
+  private Annotation getSecurityAnnotation(final FacesContext facesContext, final UIComponent component,
+      final String expression) {
+    Annotation securityAnnotation = null;
+
     if (cache.containsKey(expression)) {
       final Object obj = cache.get(expression);
       if (obj instanceof Annotation) {
-        return (Annotation) obj;
+        securityAnnotation = (Annotation) obj;
       }
-      return null;
     } else {
-      Annotation securityAnnotation = null;
       final Matcher matcher = PATTERN.matcher(expression);
       if (matcher.matches()) {
         final String beanString = matcher.group(1);
         final String methodString = matcher.group(2);
 
-        final ELContext elContext = facesContext.getELContext();
-        final Object bean = elContext
-            .getELResolver().getValue(elContext, null, beanString);
+        final Object bean = getBean(facesContext, beanString);
         if (bean != null) {
           // try first from method
           final List<Method> methods = findMethods(bean, methodString);
@@ -152,11 +155,62 @@ public class AuthorizationHelper {
       if (LOG.isInfoEnabled()) {
         LOG.info("Security annotation '{}' saved for expression '{}'", securityAnnotation, expression);
       }
+    }
+
+    if (securityAnnotation == NULL_VALUE && expression.contains(CC_ATTRS)) {
+
+      UIComponent compositeComponent = getParentCompositeComponent(component);
+      if (compositeComponent != null) {
+        final int attrNameStart = expression.indexOf(CC_ATTRS) + CC_ATTRS.length();
+        final int attrNameEnd = attrNameStart + expression.substring(attrNameStart).indexOf(".");
+        final String attrName = expression.substring(attrNameStart, attrNameEnd);
 
+        final String ccExpression = compositeComponent.getValueExpression(attrName).getExpressionString();
+        final int bracketStart = ccExpression.indexOf('{');
+        final int bracketEnd = ccExpression.indexOf("}");
+        final String trimmedCcExpression = ccExpression.substring(bracketStart + 1, bracketEnd).trim();
+
+        return getSecurityAnnotation(facesContext, component,
+            expression.replace(CC_ATTRS + attrName, trimmedCcExpression));
+      } else {
+        return securityAnnotation;
+      }
+    } else {
       return securityAnnotation;
     }
   }
 
+  private Object getBean(final FacesContext facesContext, final String beanName) {
+    BeanManager beanManager = (BeanManager) FacesContext.getCurrentInstance().getExternalContext().getApplicationMap()
+        .get(BeanManager.class.getName());
+
+    Object bean = null;
+    for (final Bean<?> entry : beanManager.getBeans(beanName)) {
+      if (bean == null) {
+        bean = entry;
+      } else {
+        LOG.warn("Bean name ambiguous: '{}'", beanName);
+      }
+    }
+
+    if (bean == null) {
+      final ELContext elContext = facesContext.getELContext();
+      bean = elContext.getELResolver().getValue(elContext, null, beanName);
+    }
+
+    return bean;
+  }
+
+  private UIComponent getParentCompositeComponent(final UIComponent component) {
+    if (component == null) {
+      return null;
+    } else if (UIComponent.isCompositeComponent(component)) {
+      return component;
+    } else {
+      return getParentCompositeComponent(component.getParent());
+    }
+  }
+
   private Annotation getSecurityAnnotations(final AnnotatedElement annotatedElement) {
     Annotation annotation = annotatedElement.getAnnotation(RolesAllowed.class);
     if (annotation != null) {
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/RoleController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/RoleController.java
index 6d6fb60..a121b44 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/RoleController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/RoleController.java
@@ -23,6 +23,7 @@ import javax.annotation.security.RolesAllowed;
 import javax.enterprise.context.SessionScoped;
 import javax.inject.Named;
 import java.io.Serializable;
+import java.time.LocalTime;
 
 @SessionScoped
 @Named
@@ -30,6 +31,7 @@ public class RoleController implements Serializable {
 
   private String text;
   private static final String OUTCOME_ADMIN = "admin";
+  private String time;
 
   public String getText() {
     return text;
@@ -53,4 +55,14 @@ public class RoleController implements Serializable {
   public String admin() {
     return OUTCOME_ADMIN;
   }
+
+  public String getTime() {
+    return time;
+  }
+
+  @RolesAllowed({"demo-admin", "demo-guest"})
+  public String refreshTime() {
+    time = LocalTime.now().toString();
+    return null;
+  }
 }
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/5500-compositeComponent/2008-roles-allowed/Roles_Allowed.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/5500-compositeComponent/2008-roles-allowed/Roles_Allowed.test.js
new file mode 100644
index 0000000..5dc2110
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/5500-compositeComponent/2008-roles-allowed/Roles_Allowed.test.js
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {elementByIdFn, querySelectorFn} from "/script/tobago-test.js";
+import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
+
+it("RolesAllowed as 'guest'", function (done) {
+  const userFn = elementByIdFn("page:mainForm:username::field");
+  const passwordFn = elementByIdFn("page:mainForm:password::field");
+  const loginFn = elementByIdFn("page:mainForm:login");
+  const logoutFn = elementByIdFn("page:mainForm:logout");
+  const outputLabelFn = querySelectorFn("#page\\:mainForm\\:output label");
+  const outputValueFn = querySelectorFn("#page\\:mainForm\\:output .form-control-plaintext");
+  const buttonFn = elementByIdFn("page:mainForm:button");
+  const ccOutputLabelFn = querySelectorFn("#page\\:mainForm\\:ccRolesTest\\:out label");
+  const ccOutputValueFn = querySelectorFn("#page\\:mainForm\\:ccRolesTest\\:out .form-control-plaintext");
+  const ccButtonFn = elementByIdFn("page:mainForm:ccRolesTest:submit");
+
+  const url = document.getElementById("page:testframe").contentWindow.location.href;
+  let time;
+
+  const test = new JasmineTestTool(done);
+  test.setup(() => loginFn(), null, "click", logoutFn);
+
+  test.do(() => expect(loginFn()).not.toBeNull());
+  test.do(() => expect(outputLabelFn().textContent).toBe("Output label"));
+  test.do(() => expect(buttonFn().disabled).toBeTrue());
+  test.do(() => expect(ccOutputLabelFn().textContent).toBe("Label"));
+  test.do(() => expect(ccButtonFn().disabled).toBeTrue());
+
+  test.do(() => userFn().value = "guest");
+  test.do(() => passwordFn().value = "guest");
+  test.event("click", loginFn, () => !loginFn());
+  test.do(() => document.getElementById("page:testframe").contentWindow.location.href = url);
+  test.wait(() => logoutFn());
+
+  test.do(() => expect(logoutFn()).not.toBeNull());
+  test.do(() => expect(outputLabelFn().textContent).toBe("Output label"));
+  test.do(() => expect(buttonFn().disabled).toBeFalse());
+  test.do(() => expect(ccOutputLabelFn().textContent).toBe("Label"));
+  test.do(() => expect(ccButtonFn().disabled).toBeFalse());
+
+  test.do(() => time = outputValueFn().textContent);
+  test.event("click", buttonFn, () => outputValueFn().textContent !== time);
+  test.do(() => expect(outputValueFn().textContent).not.toBe(time));
+  test.do(() => expect(ccOutputValueFn().textContent).not.toBe(time));
+
+  test.do(() => time = ccOutputValueFn().textContent);
+  test.event("click", ccButtonFn, () => ccOutputValueFn().textContent !== time);
+  test.do(() => expect(outputValueFn().textContent).not.toBe(time));
+  test.do(() => expect(ccOutputValueFn().textContent).not.toBe(time));
+
+  test.event("click", logoutFn, () => loginFn());
+  test.do(() => expect(loginFn()).not.toBeNull());
+
+  test.start();
+});
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/5500-compositeComponent/2008-roles-allowed/Roles_Allowed.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/5500-compositeComponent/2008-roles-allowed/Roles_Allowed.xhtml
new file mode 100644
index 0000000..83d86a1
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/5500-compositeComponent/2008-roles-allowed/Roles_Allowed.xhtml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<ui:composition template="/main.xhtml"
+                xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:dc="http://java.sun.com/jsf/composite/demo-components"
+                xmlns:tc="http://myfaces.apache.org/tobago/component"
+                xmlns:ui="http://java.sun.com/jsf/facelets">
+  <ui:param name="title" value="Composite Components"/>
+
+  <tc:box label="Login">
+    <tc:panel rendered="#{request.userPrincipal == null}">
+      <tc:in id="username" value="#{loginController.username}" label="User" required="true"/>
+      <tc:in id="password" value="#{loginController.password}" password="true" label="Password" required="true"/>
+      <tc:button id="login" action="#{loginController.login}" label="Login"/>
+    </tc:panel>
+    <tc:panel rendered="#{request.userPrincipal != null}">
+      <tc:out value="logged in as '#{request.userPrincipal.name}'"/>
+      <tc:button id="logout" label="Logout" action="#{loginController.logout}"/>
+    </tc:panel>
+  </tc:box>
+
+  <tc:section label="No Composite Component">
+    <tc:out id="output" label="Output label" value="#{roleController.time}"/>
+    <tc:button id="button" label="Reload" action="#{roleController.refreshTime}"/>
+  </tc:section>
+
+  <tc:section label="RolesTest Composite Component">
+    <dc:rolesTest id="ccRolesTest" label="Label" controller="#{roleController}"/>
+  </tc:section>
+</ui:composition>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/resources/demo-components/rolesTest.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/resources/demo-components/rolesTest.xhtml
new file mode 100644
index 0000000..e0e0cb9
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/resources/demo-components/rolesTest.xhtml
@@ -0,0 +1,31 @@
+<?xml version = "1.0" encoding = "UTF-8"?>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<!DOCTYPE html
+        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns:cc="http://java.sun.com/jsf/composite"
+      xmlns:tc="http://myfaces.apache.org/tobago/component">
+<cc:interface>
+  <cc:attribute name="label"/>
+  <cc:attribute name="controller" type="RoleController"/>
+</cc:interface>
+<cc:implementation>
+  <tc:out id="out" label="#{cc.attrs.label}" value="#{cc.attrs.controller.time}"/>
+  <tc:button id="submit" label="Reload" action="#{cc.attrs.controller.refreshTime}"/>
+</cc:implementation>
+</html>