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>