Skip to content
Browse files

Created Freemarker tags for Apache Shiro, these mirror the JSP tags

provided by Shiro itself. See README.md for usage.
  • Loading branch information...
0 parents commit b876e1a2b72d9294a1cacedb5b22316eb0441881 @jagregory committed
3 .gitignore
@@ -0,0 +1,3 @@
+target
+.idea
+*.iml
23 README.md
@@ -0,0 +1,23 @@
+# Apache Shiro tags for Freemarker
+
+[Apache Shiro](http://shiro.apache.org/) comes with some [handy JSP tags](http://shiro.apache.org/jsp-tag-library.html) for doing things like only showing content for anonymous users, logged in users, etc... I'm using [Freemarker](http://freemarker.sourceforge.net/) and didn't want to take a dependency on JSP just for Shiro, so I rewrote the tags for Freemarker.
+
+## Install
+
+Either download the [dist/shiro-freemarker-tags-0.1-SNAPSHOT.jar](/jagregory/shiro-freemarker-tags/raw/master/dist/shiro-freemarker-tags-0.1-SNAPSHOT.jar) or take all the java files and stick them in your project. Simple.
+
+If there's enough demand, I could put this up on Maven.
+
+## Usage
+
+Declare a shared variable called "shiro", and assign it to an instance of the ShiroTags class.
+
+ cfg.setSharedVariable("shiro", new ShiroTags());
+
+You should then be able to use the tags in your Freemarker templates.
+
+ <@shiro.guest>Hello guest!</@shiro.guest>
+
+## License
+
+Do what you want with it, just don't blame me if it breaks anything.
BIN dist/shiro-freemarker-tags-0.1-SNAPSHOT.jar
Binary file not shown.
32 pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>shiro-freemarker-tags</artifactId>
+ <groupId>com.jagregory</groupId>
+ <version>0.1-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-all</artifactId>
+ <version>1.1.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>2.3.18</version>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>central</id>
+ <name>Maven2 Repository</name>
+ <url>http://repo1.maven.org/maven2/</url>
+ </repository>
+ </repositories>
+</project>
44 src/main/java/com/jagregory/shiro/freemarker/AuthenticatedTag.java
@@ -0,0 +1,44 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.log.Logger;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * JSP tag that renders the tag body only if the current user has executed a <b>successful</b> authentication attempt
+ * <em>during their current session</em>.
+ *
+ * <p>This is more restrictive than the {@link UserTag}, which only
+ * ensures the current user is known to the system, either via a current login or from Remember Me services,
+ * which only makes the assumption that the current user is who they say they are, and does not guarantee it like
+ * this tag does.
+ *
+ * <p>The logically opposite tag of this one is the {@link NotAuthenticatedTag}
+ *
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.AuthenticatedTag}</p>
+ *
+ * @since 0.2
+ */
+public class AuthenticatedTag extends SecureTag {
+ private static final Logger log = Logger.getLogger("AuthenticatedTag");
+
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ if (getSubject() != null && getSubject().isAuthenticated()) {
+ if (log.isDebugEnabled()) {
+ log.debug("Subject exists and is authenticated. Tag body will be evaluated.");
+ }
+
+ renderBody(env, body);
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("Subject does not exist or is not authenticated. Tag body will not be evaluated.");
+ }
+ }
+ }
+}
42 src/main/java/com/jagregory/shiro/freemarker/GuestTag.java
@@ -0,0 +1,42 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.log.Logger;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * JSP tag that renders the tag body if the current user <em>is not</em> known to the system, either because they
+ * haven't logged in yet, or because they have no 'RememberMe' identity.
+ *
+ * <p>The logically opposite tag of this one is the {@link UserTag}. Please read that class's JavaDoc as it explains
+ * more about the differences between Authenticated/Unauthenticated and User/Guest semantic differences.
+ *
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.GuestTag}</p>
+ *
+ * @since 0.9
+ */
+public class GuestTag extends SecureTag {
+ private static final Logger log = Logger.getLogger("AuthenticatedTag");
+
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ if (getSubject() == null || getSubject().getPrincipal() == null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Subject does not exist or does not have a known identity (aka 'principal'). " +
+ "Tag body will be evaluated.");
+ }
+
+ renderBody(env, body);
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("Subject exists or has a known identity (aka 'principal'). " +
+ "Tag body will not be evaluated.");
+ }
+ }
+ }
+}
51 src/main/java/com/jagregory/shiro/freemarker/HasAnyRolesTag.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package com.jagregory.shiro.freemarker;
+
+import org.apache.shiro.subject.Subject;
+
+
+/**
+ * Displays body content if the current user has any of the roles specified.
+ *
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.HasAnyRolesTag}</p>
+ *
+ * @since 0.2
+ */
+public class HasAnyRolesTag extends RoleTag {
+ // Delimeter that separates role names in tag attribute
+ private static final String ROLE_NAMES_DELIMETER = ",";
+
+ protected boolean showTagBody(String roleNames) {
+ boolean hasAnyRole = false;
+ Subject subject = getSubject();
+
+ if (subject != null) {
+ // Iterate through roles and check to see if the user has one of the roles
+ for (String role : roleNames.split(ROLE_NAMES_DELIMETER)) {
+ if (subject.hasRole(role.trim())) {
+ hasAnyRole = true;
+ break;
+ }
+ }
+ }
+
+ return hasAnyRole;
+ }
+}
12 src/main/java/com/jagregory/shiro/freemarker/HasPermissionTag.java
@@ -0,0 +1,12 @@
+package com.jagregory.shiro.freemarker;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.HasPermissionTag}</p>
+ *
+ * @since 0.1
+ */
+public class HasPermissionTag extends PermissionTag {
+ protected boolean showTagBody(String p) {
+ return isPermitted(p);
+ }
+}
10 src/main/java/com/jagregory/shiro/freemarker/HasRoleTag.java
@@ -0,0 +1,10 @@
+package com.jagregory.shiro.freemarker;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.HasRoleTag}</p>
+ */
+public class HasRoleTag extends RoleTag {
+ protected boolean showTagBody(String roleName) {
+ return getSubject() != null && getSubject().hasRole(roleName);
+ }
+}
10 src/main/java/com/jagregory/shiro/freemarker/LacksPermissionTag.java
@@ -0,0 +1,10 @@
+package com.jagregory.shiro.freemarker;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.LacksPermissionTag}</p>
+ */
+public class LacksPermissionTag extends PermissionTag {
+ protected boolean showTagBody(String p) {
+ return !isPermitted(p);
+ }
+}
11 src/main/java/com/jagregory/shiro/freemarker/LacksRoleTag.java
@@ -0,0 +1,11 @@
+package com.jagregory.shiro.freemarker;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.LacksRoleTag}</p>
+ */
+public class LacksRoleTag extends RoleTag {
+ protected boolean showTagBody(String roleName) {
+ boolean hasRole = getSubject() != null && getSubject().hasRole(roleName);
+ return !hasRole;
+ }
+}
32 src/main/java/com/jagregory/shiro/freemarker/NotAuthenticatedTag.java
@@ -0,0 +1,32 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.log.Logger;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * Freemarker tag that renders the tag body only if the current user has <em>not</em> executed a successful authentication
+ * attempt <em>during their current session</em>.
+ *
+ * <p>The logically opposite tag of this one is the {@link org.apache.shiro.web.tags.AuthenticatedTag}.
+ *
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.NotAuthenticatedTag}</p>
+ */
+public class NotAuthenticatedTag extends SecureTag {
+ static final Logger log = Logger.getLogger("NotAuthenticatedTag");
+
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ if (getSubject() == null || !getSubject().isAuthenticated()) {
+ log.debug("Subject does not exist or is not authenticated. Tag body will be evaluated.");
+ renderBody(env, body);
+ } else {
+ log.debug("Subject exists and is authenticated. Tag body will not be evaluated.");
+ }
+ }
+}
42 src/main/java/com/jagregory/shiro/freemarker/PermissionTag.java
@@ -0,0 +1,42 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModelException;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.PermissionTag}</p>
+ */
+public abstract class PermissionTag extends SecureTag {
+ String getName(Map params) {
+ return getParam(params, "name");
+ }
+
+ @Override
+ protected void verifyParameters(Map params) throws TemplateModelException {
+ String permission = getName(params);
+
+ if (permission == null || permission.length() == 0) {
+ throw new TemplateModelException("The 'name' tag attribute must be set.");
+ }
+ }
+
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ String p = getName(params);
+
+ boolean show = showTagBody(p);
+ if (show) {
+ renderBody(env, body);
+ }
+ }
+
+ protected boolean isPermitted(String p) {
+ return getSubject() != null && getSubject().isPermitted(p);
+ }
+
+ protected abstract boolean showTagBody(String p);
+}
119 src/main/java/com/jagregory/shiro/freemarker/PrincipalTag.java
@@ -0,0 +1,119 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.log.Logger;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModelException;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <p>Tag used to print out the String value of a user's default principal,
+ * or a specific principal as specified by the tag's attributes.</p>
+ *
+ * <p> If no attributes are specified, the tag prints out the <tt>toString()</tt>
+ * value of the user's default principal. If the <tt>type</tt> attribute
+ * is specified, the tag looks for a principal with the given type. If the
+ * <tt>property</tt> attribute is specified, the tag prints the string value of
+ * the specified property of the principal. If no principal is found or the user
+ * is not authenticated, the tag displays nothing unless a <tt>defaultValue</tt>
+ * is specified.</p>
+ *
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.PrincipalTag}</p>
+ *
+ * @since 0.2
+ */
+public class PrincipalTag extends SecureTag {
+ static final Logger log = Logger.getLogger("PrincipalTag");
+
+ /**
+ * The type of principal to be retrieved, or null if the default principal should be used.
+ */
+ String getType(Map params) {
+ return getParam(params, "type");
+ }
+
+ /**
+ * The property name to retrieve of the principal, or null if the <tt>toString()</tt> value should be used.
+ */
+ String getProperty(Map params) {
+ return getParam(params, "property");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ String result = null;
+
+ if (getSubject() != null) {
+ // Get the principal to print out
+ Object principal;
+
+ if (getType(params) == null) {
+ principal = getSubject().getPrincipal();
+ } else {
+ principal = getPrincipalFromClassName(params);
+ }
+
+ // Get the string value of the principal
+ if (principal != null) {
+ String property = getProperty(params);
+
+ if (property == null) {
+ result = principal.toString();
+ } else {
+ result = getPrincipalProperty(principal, property);
+ }
+ }
+ }
+
+ // Print out the principal value if not null
+ if (result != null) {
+ try {
+ env.getOut().write(result);
+ } catch (IOException ex) {
+ throw new TemplateException("Error writing ["+result+"] to Freemarker.", ex, env);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ Object getPrincipalFromClassName(Map params) {
+ String type = getType(params);
+
+ try {
+ Class cls = Class.forName(type);
+
+ return getSubject().getPrincipals().oneByType(cls);
+ } catch (ClassNotFoundException ex) {
+ log.error("Unable to find class for name ["+type+"]", ex);
+ }
+
+ return null;
+ }
+
+ String getPrincipalProperty(Object principal, String property) throws TemplateModelException {
+ try {
+ BeanInfo beanInfo = Introspector.getBeanInfo(principal.getClass());
+
+ // Loop through the properties to get the string value of the specified property
+ for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
+ if (propertyDescriptor.getName().equals(property)) {
+ Object value = propertyDescriptor.getReadMethod().invoke(principal, (Object[]) null);
+
+ return String.valueOf(value);
+ }
+ }
+
+ // property not found, throw
+ throw new TemplateModelException("Property ["+property+"] not found in principal of type ["+principal.getClass().getName()+"]");
+ } catch (Exception ex) {
+ throw new TemplateModelException("Error reading property ["+property+"] from principal of type ["+principal.getClass().getName()+"]", ex);
+ }
+ }
+}
26 src/main/java/com/jagregory/shiro/freemarker/RoleTag.java
@@ -0,0 +1,26 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.RoleTag}</p>
+ */
+public abstract class RoleTag extends SecureTag {
+ String getName(Map params) {
+ return getParam(params, "name");
+ }
+
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ boolean show = showTagBody(getName(params));
+ if (show) {
+ renderBody(env, body);
+ }
+ }
+
+ protected abstract boolean showTagBody(String roleName);
+}
43 src/main/java/com/jagregory/shiro/freemarker/SecureTag.java
@@ -0,0 +1,43 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.template.*;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.SecureTag}</p>
+ */
+public abstract class SecureTag implements TemplateDirectiveModel {
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
+ verifyParameters(params);
+ render(env, params, body);
+ }
+
+ public abstract void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException;
+
+ protected String getParam(Map params, String name) {
+ Object value = params.get(name);
+
+ if (value instanceof SimpleScalar) {
+ return ((SimpleScalar)value).getAsString();
+ }
+
+ return null;
+ }
+
+ protected Subject getSubject() {
+ return SecurityUtils.getSubject();
+ }
+
+ protected void verifyParameters(Map params) throws TemplateModelException {
+ }
+
+ protected void renderBody(Environment env, TemplateDirectiveBody body) throws IOException, TemplateException {
+ if (body != null) {
+ body.render(env.getOut());
+ }
+ }
+}
23 src/main/java/com/jagregory/shiro/freemarker/ShiroTags.java
@@ -0,0 +1,23 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.template.SimpleHash;
+
+/**
+ * Shortcut for injecting the tags into Freemarker
+ *
+ * <p>Usage: cfg.setSharedVeriable("shiro", new ShiroTags());</p>
+ */
+public class ShiroTags extends SimpleHash {
+ public ShiroTags() {
+ put("authenticated", new AuthenticatedTag());
+ put("guest", new GuestTag());
+ put("hasAnyRoles", new HasAnyRolesTag());
+ put("hasPermission", new HasPermissionTag());
+ put("hasRole", new HasRoleTag());
+ put("lacksPermission", new LacksPermissionTag());
+ put("lacksRole", new LacksRoleTag());
+ put("notAuthenticated", new NotAuthenticatedTag());
+ put("principal", new PrincipalTag());
+ put("user", new UserTag());
+ }
+}
37 src/main/java/com/jagregory/shiro/freemarker/UserTag.java
@@ -0,0 +1,37 @@
+package com.jagregory.shiro.freemarker;
+
+import freemarker.core.Environment;
+import freemarker.log.Logger;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateException;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Freemarker tag that renders the tag body if the current user known to the system, either from a successful login attempt
+ * (not necessarily during the current session) or from 'RememberMe' services.
+ *
+ * <p><b>Note:</b> This is <em>less</em> restrictive than the <code>AuthenticatedTag</code> since it only assumes
+ * the user is who they say they are, either via a current session login <em>or</em> via Remember Me services, which
+ * makes no guarantee the user is who they say they are. The <code>AuthenticatedTag</code> however
+ * guarantees that the current user has logged in <em>during their current session</em>, proving they really are
+ * who they say they are.
+ *
+ * <p>The logically opposite tag of this one is the {@link org.apache.shiro.web.tags.GuestTag}.
+ *
+ * <p>Equivalent to {@link org.apache.shiro.web.tags.UserTag}</p>
+ */
+public class UserTag extends SecureTag {
+ static final Logger log = Logger.getLogger("UserTag");
+
+ @Override
+ public void render(Environment env, Map params, TemplateDirectiveBody body) throws IOException, TemplateException {
+ if (getSubject() != null && getSubject().getPrincipal() != null) {
+ log.debug("Subject has known identity (aka 'principal'). Tag body will be evaluated.");
+ renderBody(env, body);
+ } else {
+ log.debug("Subject does not exist or have a known identity (aka 'principal'). Tag body will not be evaluated.");
+ }
+ }
+}

0 comments on commit b876e1a

Please sign in to comment.
Something went wrong with that request. Please try again.