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 11:21:14 UTC

[myfaces-tobago] branch tobago-4.x 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 tobago-4.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


The following commit(s) were added to refs/heads/tobago-4.x by this push:
     new 38696b5  fix(AuthorizationHelper): RolesAllowed annotation for composite-components
38696b5 is described below

commit 38696b501e812187a1b9c3ed79585c2dc1b3c5ad
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
    * qunit test added
    
    Issue: TOBAGO-2008
---
 .../internal/component/AbstractUICommandBase.java  |  6 +-
 .../tobago/internal/util/AuthorizationHelper.java  | 46 ++++++++++--
 .../tobago/example/demo/RoleController.java        | 12 +++
 .../2008-roles-allowed/Roles_Allowed.test.js       | 86 ++++++++++++++++++++++
 .../2008-roles-allowed/Roles_Allowed.xhtml         | 47 ++++++++++++
 .../resources/demo-components/rolesTest.xhtml      | 31 ++++++++
 6 files changed, 217 insertions(+), 11 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 1045901..9169507 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
@@ -117,11 +117,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 07b9f97..cb71b00 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
@@ -26,6 +26,7 @@ import javax.annotation.security.DenyAll;
 import javax.annotation.security.PermitAll;
 import javax.annotation.security.RolesAllowed;
 import javax.faces.bean.ManagedBean;
+import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
@@ -49,6 +50,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() {
@@ -83,9 +86,9 @@ public class AuthorizationHelper {
         facesContext.getELContext().getELResolver().getValue(facesContext.getELContext(), 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;
     }
@@ -118,15 +121,16 @@ 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);
@@ -167,11 +171,41 @@ 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 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..bad09d3
--- /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,86 @@
+/*
+ * 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.
+ */
+
+QUnit.test("RolesAllowed as 'guest'", function (assert) {
+  var userFn = jQueryFrameFn("#page\\:mainForm\\:username\\:\\:field");
+  var passwordFn = jQueryFrameFn("#page\\:mainForm\\:password\\:\\:field");
+  var loginFn = jQueryFrameFn("#page\\:mainForm\\:login");
+  var logoutFn = jQueryFrameFn("#page\\:mainForm\\:logout");
+  var outputLabelFn = jQueryFrameFn("#page\\:mainForm\\:output label");
+  var outputValueFn = jQueryFrameFn("#page\\:mainForm\\:output .tobago-out");
+  var buttonFn = jQueryFrameFn("#page\\:mainForm\\:button");
+  var ccOutputLabelFn = jQueryFrameFn("#page\\:mainForm\\:ccRolesTest\\:out label");
+  var ccOutputValueFn = jQueryFrameFn("#page\\:mainForm\\:ccRolesTest\\:out .tobago-out");
+  var ccButtonFn = jQueryFrameFn("#page\\:mainForm\\:ccRolesTest\\:submit");
+
+  var url = document.getElementById("page:testframe").contentWindow.location.href;
+  var time;
+
+  assert.expect(5);
+  var TTT = new TobagoTestTool(assert);
+  TTT.asserts(5, function () {
+    assert.equal(loginFn().length, 1, "user must be logged out");
+    assert.equal(outputLabelFn().text(), "Output label");
+    assert.equal(buttonFn().attr("disabled"), "disabled");
+    assert.equal(ccOutputLabelFn().text(), "Label");
+    assert.equal(ccButtonFn().attr("disabled"), "disabled");
+  });
+  TTT.action(function () {
+    userFn().val("guest");
+    passwordFn().val("guest");
+    loginFn().click();
+  });
+  TTT.waitForResponse();
+  TTT.action(function () {
+    document.getElementById("page:testframe").contentWindow.location.href = url;
+  });
+  TTT.waitForResponse();
+  TTT.waitMs(2000);
+  TTT.asserts(5, function () {
+    assert.equal(logoutFn().length, 1, "user must be logged in");
+    assert.equal(outputLabelFn().text(), "Output label");
+    assert.equal(buttonFn().attr("disabled"), undefined);
+    assert.equal(ccOutputLabelFn().text(), "Label");
+    assert.equal(ccButtonFn().attr("disabled"), undefined);
+  });
+  TTT.action(function () {
+    time = outputValueFn().text();
+    buttonFn().click();
+  });
+  TTT.waitForResponse();
+  TTT.asserts(2, function () {
+    assert.notEqual(outputValueFn().text(), time);
+    assert.notEqual(ccOutputValueFn().text(), time);
+  });
+  TTT.action(function () {
+    time = ccOutputValueFn().text();
+    ccButtonFn().click();
+  });
+  TTT.waitForResponse();
+  TTT.asserts(2, function () {
+    assert.notEqual(outputValueFn().text(), time);
+    assert.notEqual(ccOutputValueFn().text(), time);
+  });
+  TTT.action(function () {
+    logoutFn().click();
+  });
+  TTT.waitForResponse();
+  TTT.asserts(1, function () {
+    assert.equal(loginFn().length, 1, "user must be logged out");
+  });
+  TTT.startTest();
+});
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>