Friday, April 28, 2017

Extracting active logged-in users and active authentication tickets from Alfresco


Sometimes an Alfresco Administrator might need to track active logged-in users in the system for audit purpose or for planning maintenance activities. There can be many other use-cases depending of type of usage or organization policies.

Alfresco doesn't provide this kind of feature for admins out of the box as of now. However Alfresco 5.2 version on-wards they are providing Support Tools feature which can provide various options including this particular use-case.

Support Tools is an Add-On, which is available on GitHub as alfresco-support-tools which can be installed as a module as well. Alfresco 5.2 (enterprise) on-wards alfresco has integrated this add-on out of the box.

See here: Support Tools

Here we are going to use Alfresco's TicketComponent service to get the active user details and active ticket details.
We will create a java backed web-script which will return the basic details about the active users, total active user count, total no. of active tickets etc.

Follow the below given steps:

  • Create following folder structure under "/alfresco/extension/templates/webscripts" directory
    • Create a folder named "com" under "webscripts"
    • Under folder "com", create a folder named "github"
    • Under folder "github", create a folder named "abhinavmishra14"
    • Under folder "abhinavmishra14", create a folder named "audit"
<webscript>
	<shortname>Active Users</shortname>
	<description>This webscript returns the active users logged-in into
		Alfresco.
		Sample response:
		{
		activeTicketsCount: 2,
		activeUsers: "["admin","abhinav@gmail.com "]",
		activeUsersCount: 2,
		_comment: "Active user count may be lower than the ticket count, since a user
		can have more than one ticket/session. Ticket count may be higher than
		the active user count, since a user can have more than one
		ticket/session."
		}
	</description>
	<url>/audit/getActiveUsers</url>
	<format default="json" />
	<authentication>admin</authentication>
	<family>Audit</family>
</webscript>
 
  • Create a freemarker template “getActiveUsers.get.json.ftl” which is used to generate the view under "com > github > abhinavmishra14 > audit" folder.
<#escape x as jsonUtils.encodeJSONString(x)> ${response} </#escape>

  • Create a java package "com.github.abhinavmishra14.audit.webscript"
  • Create java class "GetActiveUsersWebscript" under the "com.github.abhinavmishra14.audit.webscript" package.
/*
 * Created By: Abhinav Kumar Mishra
 * Copyright &copy; 2017. Abhinav Kumar Mishra. 
 * All rights reserved.
 * 
 * Licensed 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.github.abhinavmishra14.audit.webscript;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.TicketComponent;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * The Class GetActiveUsersWebscript.
 */
public class GetActiveUsersWebscript extends DeclarativeWebScript {

	/** The Constant LOGGER. */
	private static final Logger LOGGER = LoggerFactory.getLogger(GetActiveUsersWebscript.class);

	/** The Constant ACTIVE_USERS. */
	private static final String ACTIVE_USERS = "activeUsers";

	/** The Constant ACTIVE_USERS_COUNT. */
	private static final String ACTIVE_USERS_COUNT = "activeUsersCount";

	/** The Constant ACTIVE_TICKETS_COUNT. */
	private static final String ACTIVE_TICKETS_COUNT = "activeTicketsCount";

	/** The Constant COMMENT_DATA. */
	private static final String COMMENT_DATA = "_comment";

	/** The Constant RESPONSE. */
	private static final String RESPONSE = "response";

	/** The ticket component. */
	private final TicketComponent ticketComponent;

	/**
	 * The Constructor.
	 *
	 * @param ticketComponent the ticket component
	 */
	public GetActiveUsersWebscript(final TicketComponent ticketComponent) {
		super();
		this.ticketComponent = ticketComponent;
	}

	/* (non-Javadoc)
	 * @see org.springframework.extensions.webscripts.DeclarativeWebScript#executeImpl(org.springframework.extensions.webscripts.WebScriptRequest, org.springframework.extensions.webscripts.Status, org.springframework.extensions.webscripts.Cache)
	 */
	@Override
	public Map<String, Object> executeImpl(final WebScriptRequest req,
			final Status status, final Cache cache) {
		if(LOGGER.isDebugEnabled()) {
			LOGGER.debug("Extracting active users..");
		}
		final Map<String, Object> model = new ConcurrentHashMap<String, Object>(3);
		try {
			//get nonExpiredOnly users with tickets
			final Set<String> activeUsers = ticketComponent.getUsersWithTickets(true);
			final ObjectMapper objMapper = new ObjectMapper();
			objMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);

			if (activeUsers != null && !activeUsers.isEmpty()) {
				final JSONObject activeUsersJson = new JSONObject();
				//This may be lower than the ticket count, since a user can have more than one
				// ticket/session
				activeUsersJson.put(ACTIVE_USERS, objMapper.writeValueAsString(activeUsers));
				activeUsersJson.put(ACTIVE_USERS_COUNT, activeUsers.size());

				//This may be higher than the user count, since a user can have more than one   
				// ticket/session
				//get nonExpiredOnly ticket count
				activeUsersJson.put(ACTIVE_TICKETS_COUNT, ticketComponent.countTickets(true));

				activeUsersJson.put(COMMENT_DATA, "Active user count may be lower than the ticket count, since a user can have more than one ticket/session. Ticket count may be higher than the active user count, since a user can have more than one ticket/session.");
				model.put(RESPONSE, activeUsersJson);
			}
		} catch (JsonProcessingException | JSONException excp) {
			LOGGER.error("Exception occurred while preparing json for active users ", excp);
			throw new WebScriptException(
					Status.STATUS_INTERNAL_SERVER_ERROR, excp.getMessage(), excp);
		} catch (AlfrescoRuntimeException alfErr) {
			LOGGER.error("Unexpected error occurred while getting active users ", alfErr);
			throw new WebScriptException(
					Status.STATUS_INTERNAL_SERVER_ERROR, alfErr.getMessage(), alfErr);
		}
		if(LOGGER.isDebugEnabled()) {
			LOGGER.debug("Extracted active users.");
		}
		return model;
	}
}

<bean id="webscript.com.github.abhinavmishra14.audit.getActiveUsers.get"
	  class="com.github.abhinavmishra14.audit.webscript.GetActiveUsersWebscript"
	  parent="webscript">
  <constructor-arg ref="ticketComponent" />
</bean>

  • Build and start the alfresco instance.
  • It will return following type of response (example):
{
  "activeTicketsCount": 3,
  "activeUsersCount": 3,
  "_comment": "Active user count may be lower than the ticket count, since a user can have more than one ticket/session. Ticket count may be higher than the active user count, since a user can have more than one ticket/session.",
  "activeUsers": "[\"test2\",\"admin\",\"guest\"]"
}



Refer this github project in case you need to refer to the codehttps://github.com/abhinavmishra14/active-users-report

Alternatively, you can apply "active-users-report-1.0-SNAPSHOT.amp" to your alfresco installation as an add-on.



4 comments:

  1. you java, at line number : 70, DeserializationFeature, giving me error, as Cannot resolve symbol, did you have anything that need to be imported here?

    java is not able to compile,

    ReplyDelete
    Replies
    1. Which version of Alfresco you are using ? This code is tested with 5.0 and works with 5.x and onwards.

      I think you are missing JSON related dependencies (probably jackson-databind) which are mandatory for the code to work.

      Please make sure that you have following dependencies:

      · jackson-databind
      · jackson-core
      · jackson-annotations

      You can download these dependencies from here: http://mvnrepository.com/search?q=com.fasterxml.jackson.core

      Additionally your build path should have slf4j and log4j dependencies.

      Delete
  2. ERROR [extensions.webscripts.AbstractRuntime] [http-bio-8080-exec-12] Exception from executeScript: 00050008 Wrapped Exception (with status template): 00050011 Error during processing of the template 'The following has evaluated to null or missing:
    ==> jsonUtils.encodeJSONString(x) [in template "com/eisenvault/getActiveUsers.get.json.ftl" at line 1, column 15]

    Tip: If the failing expression is known to be legally null/missing, either specify a default value with myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing. (These only cover the last step of the expression; to cover the whole expression, use parenthessis: (myOptionVar.foo)!myDefault, (myOptionVar.foo)??

    The failing instruction:
    ==> ${response} auto-escaped [in template "com/eisenvault/getActiveUsers.get.json.ftl" at line 1, column 46]'. Please contact your system administrator.
    org.springframework.extensions.webscripts.WebScriptException: 00050008 Wrapped Exception (with status template): 00050011 Error during processing of the template 'The following has evaluated to null or missing:
    ==> jsonUtils.encodeJSONString(x) [in template "com/eisenvault/getActiveUsers.get.json.ftl" at line 1, column 15]

    Tip: If the failing expression is known to be legally null/missing, either specify a default value with myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing. (These only cover the last step of the expression; to cover the whole expression, use parenthessis: (myOptionVar.foo)!myDefault, (myOptionVar.foo)??



    getting this error in log

    ReplyDelete
    Replies
    1. See the error, it says there is null in the response. Like i mentioned there is some glicth which is not returning the required to the template. Try and debug the issue. Must be a code glitch.

      I have exported the code to git so you can also refer: https://github.com/abhinavmishra14/active-users-report

      Alternatively, just download active-users-report-1.0-SNAPSHOT.amp (https://github.com/abhinavmishra14/active-users-report/releases/download/active-users-report-v1.0/active-users-report-1.0-SNAPSHOT.amp) and install on alfresco.war file and consume the response from http://127.0.0.1:8080/alfresco/service/audit/getActiveUsers

      Delete

Thanks for your comments/Suggestions.