Adding authentication filters and using profile attributes

Introduction

The problems we're addressing are:

  • How to create a custom authentication filter
  • How to create profile attributes with the custom filter
  • How to call up the profile attribute when creating a custom data source - when we pull up the attribute values, then we'll be able to take those values and do any extra processing.

Overview of Solution

  • Create the custom authentication filter
    • Have it read the request parameters from the URL
    • Add the profile attribute values for the parameters used in the User DTO objects
  • Execute the report
    • Call the report via a URL
    • Have the custom data source use the profile attribute values to do extra processing

Create the custom authentication filter class

  • Have it implement javax.servlet.Filter and org.springframework.beans.factory.InitializingBean
  • Implement the doFilter method
  • Have it create the profile attributes based on the request parameters
    • Check if the parameter contains values necessary for a User DTO object
    • If so, then create a profile attribute based on that key-value pair
       
    package example.cds.auth;
     
       import org.apache.commons.logging.Log;
       import org.apache.commons.logging.LogFactory;
       import java.io.IOException;
       import java.util.Iterator;
       import java.util.Map;
       import java.util.Set;
     
       import javax.servlet.Filter;
       import javax.servlet.FilterChain;
       import javax.servlet.FilterConfig;
       import javax.servlet.ServletException;
       import javax.servlet.ServletRequest;
       import javax.servlet.ServletResponse;
       import javax.servlet.http.HttpServletRequest;
       import javax.servlet.http.HttpServletResponse;
     
       import org.springframework.beans.factory.InitializingBean;
       import org.springframework.security.Authentication;
       import org.springframework.security.context.SecurityContextHolder;
     
       import com.jaspersoft.jasperserver.api.metadata.user.domain.ProfileAttribute;
       import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.client.MetadataUserDetails;
       import com.jaspersoft.jasperserver.api.metadata.user.service.ProfileAttributeService;
       import com.jaspersoft.jasperserver.api.metadata.user.service.impl.ProfileAttributeServiceImpl;
     
       public class SimpleAuthFilter implements Filter, InitializingBean {
     
           private static Log log = LogFactory.getLog(SimpleAuthFilter.class);
           private static final String [] userAttribs = {"teacherId", "userType","schoolId", "districtId"};
           private ProfileAttributeService profileAttributeService;
     
           public ProfileAttributeService getProfileAttributeService() {
               return profileAttributeService;
           }
     
           public void setProfileAttributeService(
               ProfileAttributeService profileAttributeService) {
               this.profileAttributeService = profileAttributeService;
           }
     
           @Override
           public void afterPropertiesSet() throws Exception {
               // TODO Auto-generated method stub
           }
     
           @Override
           public void destroy() {
               // TODO Auto-generated method stub
           }
     
           @Override
           public void doFilter(ServletRequest request,
                                ServletResponse response,
                                FilterChain chain)
                  throws IOException, ServletException {
               // TODO Auto-generated method stub
     
               HttpServletRequest req = (HttpServletRequest) request;
               HttpServletResponse res = (HttpServletResponse) response;
     
               MetadataUserDetails user = null;
               Authentication auth = SecurityContextHolder.getContext().getAuthentication();
               if (auth != null && auth.getPrincipal() != null) {
                   if (auth.getPrincipal() instanceof MetadataUserDetails) {
                       user = (MetadataUserDetails) auth.getPrincipal();
                       Map<String, String[]> map = req.getParameterMap();
     
                       // if teacherId, userType, schoolType or districtId in param map
                       // then add them to profile attribute
                       processReqParams(map, user);
                   } else {
                       log.debug("The authentication object was not of the correct type: "
                                 + auth.getPrincipal().getClass().getName());
                   }
               }
     
               chain.doFilter(req, res);
           }
     
           @Override
           public void init(FilterConfig arg0) throws ServletException {
               // TODO Auto-generated method stub
           }
     
           // This particular method could be greatly improved; for example it could
           // first check for the existence of those attributes before trying to
           // create them, but this is meant as an example
           private void processReqParams(Map<String, String[]> map, MetadataUserDetails user) {
               Set keys = map.keySet();
               Iterator i = keys.iterator();
               while (i.hasNext()) {
                   Object o = i.next();
                   String key = (String) o;
                   String value = (String) map.get(o)[0];
                   log.debug("key = " + key + ", value = " + value);
                   if (isFromUserDTO(key) == true && value != null) {
     
                       // Create the profile attribute
                       if (profileAttributeService == null) {
                           log.debug("profileAttributeService is null :(");
                       } else {
                           log.debug("profileAttributeService is not null");
                       }
                       ProfileAttribute attrib = profileAttributeService.newProfileAttribute(null);
                       attrib.setPrincipal(user);
                       attrib.setAttrName(key);
                       attrib.setAttrValue(value);
                       profileAttributeService.putProfileAttribute(null, attrib);
     
                   } else {
                       log.debug("The key is not in the UserDTO or the value is null");
                   }
               }
           }
     
           private boolean isFromUserDTO(String key) {
               boolean isFromUserDTO = false;
     
               for (String s : userAttribs) {
                   if (key.equals(s)) {
                       isFromUserDTO = true;
                       break;
                   }
               }
               return isFromUserDTO;
           }
       }

Modify the Spring configuration file

Modify the applicationContext-security-web.xml file in the /jasperserver-pro/WEB-INF directory. Add the bean definition and add the bean id to the filterChainProxy. For example, the bean ID definition will look like this:

<bean id="SimpleAuthFilter" class="example.cds.auth.SimpleAuthFilter">
    <property name="profileAttributeService">
        <ref bean="profileAttributeService" />
    </property>
</bean>

Adding the bean ID to the filterChainProxy will look like this:

/**=httpSessionContextIntegrationFilter
    , ${bean.loggingFilter}
    , ${bean.userPreferencesFilter}
    , ${bean.authenticationProcessingFilter}
    , ${bean.userPreferencesFilter}
    , SimpleAuthFilter,${bean.basicProcessingFilter}
    , requestParameterAuthenticationFilter
    , JIAuthenticationSynchronizer
    , anonymousProcessingFilter
    , exceptionTranslationFilter
    , filterInvocationInterceptor
    , switchUserProcessingFilter

How to add profile attributes

I added profile attributes in the processReqParams method above, and it's an example of how to programmatically add profile attributes for a logged-in user. The method calls up the profileAttributeService that was defined for the SimpleAuthFilter bean, which allows us to manipulate profile attributes via the engine. You can verify that the attributes have been written to the database by consulting the jiprofileattributes table.

How to pull up the profile attributes

Once the profile attributes are stored, we can go ahead and pull them up when creating the custom data source object. The process is to:

  • Call up the data source constructor
  • Within the constructor, call up the current user object
  • Get that user's profile attributes
  • Do any extra processing at this point with the profile attribute values
  • Below you'll find an example of how to pull up the profile attributes data. It's a modified version of the CustomDataSource class that our products ships with, which you can find in the samples directory.

    public CustomDataSource() {
        MetadataUserDetails user = null;
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getPrincipal() != null) {
            if (auth.getPrincipal() instanceof MetadataUserDetails) {
                user = (MetadataUserDetails) auth.getPrincipal();
                // if teacherId, userType, schoolType or districtId in param map
                // then add them to profile attribute
                List attribs = user.getAttributes();
                int size = attribs.size();
                if (size > 0) {
                    Iterator i = attribs.iterator();
                    while (i.hasNext()) {
                        //String attrib = (String) i.next();
                        ProfileAttributeImpl attrib = (ProfileAttributeImpl) i.next();
                        String attribName = attrib.getAttrName();
                        String attribValue = attrib.getAttrValue();
                        LOG.debug("Attrib: Name = " + attribName + ", Value = " +
                                  attribValue);
                    }
                }
            } else {
                LOG.debug("The authentication object was not of the correct type: " +
                          auth.getPrincipal().getClass().getName());
            }
        }
    }

How to call the report via a URL

You can construct a URL such as the one below:

http://localhost:8080/jasperserver-pro/flow.html?_flowId=viewReportFlow&standAlone=true&the_city_distinct=Dallas&userType=teacher&_flowId=viewReportFlow&ParentFolderUri=%2Fpublic&reportUnit=%2Fpublic%2FsampleCDS_Report

This URL is based on the Custom data source example that ships with the product:

  • "the_city_distinct" is a value that gets fed into an input control. I changed the input control to "not mandatory" and the report executes without bringing up the pop-up window asking the user to populate a value for this parameter
  • "userType" is a parameter key I used to simulate an attribute we wish to work with
  • You can also add j_username and j_password parameters to perform the authentication as well.
Feedback
randomness