Skip navigation
All Places > Alfresco Content Services (ECM) > Blog > 2019 > July
2019

Alfresco provides Content REST API for downloading the content from repository as given below.

 

GET /alfresco/service/api/node/content/{store_type}/{store_id}/{id}?a=false&alf_ticket={TICKET}

 

Example:

 

http://127.0.0.1:8080/alfresco/service/api/node/content/workspace/SpacesStore/85d2a84d-271b-41b1-9449-02f5942893a0?a=false&alf_ticket=TICKET_5550fa5e9b87bead8f008e906185e023b7ce21ed

 

Where: 
a: attach. if true, force download of content as attachment. Possible values are true/false
store_type: workspace
store_id: SpacesStore
id: nodeRefId (UUID of the node)

A StoreRef is comprised of:

 

Store Protocol - that is, the type of store
Store Identifier - the id of the store

 

Example storeRefs are:

 

workspace://SpacesStore  (store_type://store_id)
version://versionStore  (store_type://store_id)
archive://SpacesStore  (store_type://store_id)
 
See here for Content API Webscript definition: content.get.desc.xml

Any external system or client can use this API to download content from Alfresco repository via an authenticated user which would be registered in Alfresco. 

If you want to know how to get auth ticket (alf_ticket) then visit: Alfresco Login REST API (https://docs.alfresco.com/5.0/references/RESTful-RepositoryLoginPost.html)

 

OOTB Download REST API will allow to download the content to any user who is registered in alfresco, since every user has consumer access to every site by default via "EVERYONE" group. But let's suppose you want to put some kind of restrictions to the Download API. Let's say for example:

1- Allow download if requesting user is authorized to download the content.
2- Want to validate the site level user role e.g. only Managers/Collaborators/Contributors can download, Consumers should not be allowed to download.
3- Want to check if user is part of DOWNLOADERS groups then allow them to download 
..... 
and so on. 
 
There could be many such cases which we can not achieve via OOTB REST API provided by alfresco. If your contents has copyrights you will definitely not allow users to download the content who are unauthorized. 
 
To handle such scenarios you need to write a custom Download webscript.
 
Alfresco provides a Webscript called "org.alfresco.repo.web.scripts.content.StreamContent" (http://dev.alfresco.com/resource/docs/java/org/alfresco/repo/web/scripts/content/StreamContent.html). 

By extending this class we can add our custom user validation logic and leave the streaming and download handling part to this OOTB Webscript.
 
So, let's take a use case where you don't want a consumer user to download the content from a site. To achieve this use case a custom webscript will be written as given below:

 

DownloadContentWebscript.java

 

package com.github.abhinavmishra14.webscript;

import java.io.IOException;
import java.util.Locale;
import java.util.Set;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.web.scripts.content.StreamContent;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;

/**
* The Class DownloadContentWebscript.
*
*/

public class DownloadContentWebscript extends StreamContent {

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

/** The content service. */
private ContentService contentService;

/** The authentication service. */
private AuthenticationService authenticationService;

/** The site service. */
private SiteService siteService;

/** The authority service. */
private AuthorityService authorityService;

/* (non-Javadoc)
  * @see org.springframework.extensions.webscripts.WebScript#execute(org.springframework.extensions.webscripts.WebScriptRequest, org.springframework.extensions.webscripts.WebScriptResponse)
  */

@Override
public void execute(final WebScriptRequest request,
   final WebScriptResponse response) throws IOException {
  LOGGER.info("Started executing DownloadContentWebscript...");
  try {
   final NodeRef nodeRef = getParameterAsNodeRef(request, "nodeRef");
   final String userName = authenticationService.getCurrentUserName();
   if(isNotAuthorised(nodeRef, userName, siteService, permissionService, authorityService)) {
    response.setStatus(401);
    response.getWriter().write("User is unauthorised to download the requested content!");
   } else {
    if(LOGGER.isDebugEnabled()) {
     LOGGER.debug("Processing the download requested by: {}", userName);
    }
    final boolean attach = Boolean.valueOf(request.getParameter("attach"));
    processDownload(request, response, nodeRef, attach, ContentModel.PROP_CONTENT);
   }
  } catch (AccessDeniedException accessDenied) {
   LOGGER.error("Access denied while downloading content", accessDenied);
   throw new WebScriptException(Status.STATUS_UNAUTHORIZED,
     accessDenied.getMessage(), accessDenied);
  } catch (IOException | AlfrescoRuntimeException
    | InvalidNodeRefException excp) {
   LOGGER.error("Exception occurred while downloading content", excp);
   throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR,
     excp.getMessage(), excp);
  }
  LOGGER.info("Existing from DownloadContentWebscript...");
}

/**
  * Process download.
  *
  * @param request the request
  * @param response the response
  * @param nodeRef the node ref
  * @param attach the attach
  * @param propertyQName the property q name
  * @throws IOException the IO exception
  */

private void processDownload(final WebScriptRequest request,
   final WebScriptResponse response, final NodeRef nodeRef, final boolean attach,
   final QName propertyQName) throws IOException {
  String userAgent = request.getHeader("User-Agent");
  userAgent = StringUtils.isNotBlank(userAgent) ? userAgent.toLowerCase(Locale.ENGLISH) : StringUtils.EMPTY;
  final boolean isClientSupported= userAgent.contains("msie")
    || userAgent.contains(" trident/")
    || userAgent.contains(" chrome/")
    || userAgent.contains(" firefox/");

  if (attach && isClientSupported) {
   String fileName = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
   if (userAgent.contains("msie") || userAgent.contains(" trident/")) {
    final String mimeType = contentService.getReader(nodeRef, propertyQName).getMimetype();
    if (!(this.mimetypeService.getMimetypes(FilenameUtils.getExtension(fileName)).contains(mimeType))) {
     fileName = FilenameUtils.removeExtension(fileName)+ FilenameUtils.EXTENSION_SEPARATOR_STR
       + this.mimetypeService.getExtension(mimeType);
    }
   }
   streamContent(request, response, nodeRef, propertyQName, attach, fileName, null);
  } else {
   streamContent(request, response, nodeRef, propertyQName, attach, null, null);
  }
}

/**
     * Create NodeRef instance from a WebScriptRequest parameter.
     *
     * @param req the req
     * @param paramName the param name
     * @return the parameter as node ref
     */

    private NodeRef getParameterAsNodeRef(final WebScriptRequest req, final String paramName) {
        final String nodeRefStr = StringUtils.trimToNull(req.getParameter(paramName));
        if (StringUtils.isBlank(nodeRefStr)) {
            throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Missing " + paramName + " parameter");
        }
        if (!NodeRef.isNodeRef(nodeRefStr)) {
            throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Incorrect format for " + paramName + " paramater");
        }
        final NodeRef nodeRef = new NodeRef(nodeRefStr);
        if (!nodeService.exists(nodeRef)) {
            throw new WebScriptException(Status.STATUS_BAD_REQUEST, paramName + " not found");
        }
        return nodeRef;
    }
  
/**
  * Checks if is not authorised.
  *
  * @param nodeRef the node ref
  * @param userName the user name
  * @param siteService the site service
  * @param permissionService the permission service
  * @param authorityService the authority service
  * @return true, if checks if is not authorised
  */

private boolean isNotAuthorised(final NodeRef nodeRef,
   final String userName, final SiteService siteService,
   final PermissionService permissionService,
   final AuthorityService authorityService) {
  boolean isNotAuthorised = false;
  final SiteInfo siteInfo = siteService.getSite(nodeRef);
  // Checking siteInfo, If it is null that means user is not a member of site and
  // hence isNotAuthorised is default to false.
  if (null != siteInfo) {
   if (siteService.isMember(siteInfo.getShortName(), userName)) {
    final Set<AccessPermission> permissions = permissionService.getAllSetPermissions(nodeRef);
    if(LOGGER.isDebugEnabled()) {
     LOGGER.debug("Checking isNotAuthorised, Available access permissions are: {}", permissions);
    }
    for (final AccessPermission permission : permissions) {
     if (permission.getPermission().equals("SiteConsumer")
       || permission.getPermission().equals("Consumer")) {
      if (permission.getAuthorityType().equals("USER")
        && permission.getAuthority().equals(userName)) {
       isNotAuthorised = true;
       break;
      } else if (permission.getAuthorityType().toString().equals("GROUP")) {
       //Set run as system user since other users including consumers can not fetch authorities
       AuthenticationUtil.setRunAsUserSystem();
       final Set<String> authorities = authorityService.getAuthoritiesForUser(userName);
       //Clear system user context and set original user context
       AuthenticationUtil.clearCurrentSecurityContext();
       AuthenticationUtil.setFullyAuthenticatedUser(userName);
       if(LOGGER.isDebugEnabled()) {
        LOGGER.debug("Checking permissions at GROUP level, user has following authorities: {}", authorities);
       }
       for (final String authority : authorities) {
        if (authority.equals(permission.getAuthority())) {
         isNotAuthorised = true;
         break;
        }
       }
      }
     }
    }
   } else {
    isNotAuthorised = true;//Not a member in the site.
   }
  }
  return isNotAuthorised;
}

/**
  * Sets the content service.
  *
  * @param contentService the content service
  */

public void setContentService(final ContentService contentService) {
  this.contentService = contentService;
}

/**
  * Sets the authentication service.
  *
  * @param authenticationService the authentication service
  */

public void setAuthenticationService(final AuthenticationService authenticationService) {
  this.authenticationService = authenticationService;
}

/**
  * Sets the site service.
  *
  * @param siteService the site service
  */

public void setSiteService(final SiteService siteService) {
  this.siteService = siteService;
}

/**
  * Sets the authority service.
  *
  * @param authorityService the authority service
  */

public void setAuthorityService(final AuthorityService authorityService) {
  this.authorityService = authorityService;
}
}

WebScript Description:
alfresco/extension/templates/webscripts/com/github/abhinavmishra14/downloadContent.get.desc.xml

 

<webscript>
<shortname>Download Content</shortname>
<description>
  <![CDATA[
      Download Content based on role and permissions check.
      Where:
      nodeRef- NodeRef of the content e.g. workspace://SpacesStore/5cee9f74-eb2a-43a4-965d-6e4fcde4fb9e
      attach (Optional)- if true, force download of content as attachment (Possible values are true/false)
     
      Sample URL:
      http://127.0.0.1:8080/alfresco/service/abhinavmishra14/downloadContent?nodeRef=workspace://SpacesStore/5cee9f74-eb2a-43a4-965d-6e4fcde4fb9e&attach=true
   ]]>

</description>
<url>/abhinavmishra14/downloadContent?nodeRef={nodeRef}&amp;attach={attach?}</url>
<format default="">argument</format>
<authentication>user</authentication>
<transaction allow="readonly" />
<family>common</family>
</webscript>

 

Bean definition:
webscript-context.xml

 

<bean id="webscript.com.github.abhinavmishra14.downloadContent.get" class="com.github.abhinavmishra14.webscript.DownloadContentWebscript" parent="webscript">
  <property name="permissionService" ref="PermissionService" />
  <property name="nodeService" ref="NodeService" />
  <property name="mimetypeService" ref="MimetypeService" />
  <property name="delegate" ref="webscript.content.streamer" />
  <property name="repository" ref="repositoryHelper" />
 
  <property name="contentService" ref="ContentService" />
  <property name="authenticationService" ref="AuthenticationService" />
  <property name="siteService" ref="SiteService" />
  <property name="authorityService" ref="AuthorityService"/>
</bean>
 

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 called alfresco-support-tools which can be installed as a module as well. It seems Alfresco 5.2 on-wards alfresco has integrated this add-on OOTB. I have not tried this add-on so not sure how it works. However the screenshots given at add-on page shows that it has good amount of features which is really useful for admins.

 

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:

 

1-      Create alfresco webscript descriptor “getActiveUsers.get.desc.xml

<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>

 

2-      Create a freemarker template “getActiveUsers.get.json.ftl” which is used to generate the view

 

<#escape x as jsonUtils.encodeJSONString(x)> ${response} </#escape>

 

3-      Create a Java webscript class “GetActiveUsersWebscript.java

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.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 COMMENT. */

private static final String COMMENT = "getActiveUsers.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;

}

 

@Override

public Map<String, Object> executeImpl(final WebScriptRequest req,

                final Status status, final Cache cache) {

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);

    }

    return model;

   }

}

 

4-      Add the bean definition for the java webscript in spring context file.

<bean id="webscript.com.github.abhinavmishra14.audit.getActiveUsers.get"

class=" com.github.abhinavmishra14.audit.webscript.GetActiveUsersWebscript" parent="webscript">

    <constructor-arg ref="ticketComponent"/>

 </bean>

 

5-      Build and start the alfresco instance.

6-      Access the service using URL: http://127.0.0.1:8080/alfresco/service/audit/getActiveUsers (You will be prompted for credentials, note that the user should be admin who is accessing this service)

7-      It will return following type of response:

{

 activeTicketsCount: 3,

 activeUsers: "["admin","test","test2@gmail.com "]",

 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."

}

We can inject the repository service dependencies using 2 methods. One method is a lower case e.g. “contentService” bean ref and other method is upper case e.g. “ContentService” bean ref, similarly we can use "nodeService" or "NodeService".

To use a service in your custom implementation, you usually use a spring config (spring context) file to inject service bean dependencies. So in your spring context file, for your class for example “com.abhinav.CustomAction”, you can either use "nodeService" [lower-case] or "NodeService" [upper-case].

 

 Method 1-

   <bean id="customAction" class="com.abhinav.CustomAction">

      <property name="nodeService">

          <!-- Lower case -->

          <ref bean="nodeService"/>

      </property>

      <property name="contentService">

          <!-- Lower case -->

          <ref bean="contentService"/>

      </property>

   </bean>

 

Method 2-

<bean id="customAction" class="com.abhinav.CustomAction">

      <property name="nodeService">

          <!-- Lower case -->

          <ref bean="NodeService"/>

      </property>

      <property name="contentService">

          <!-- Lower case -->

          <ref bean="ContentService"/>

      </property>

</bean>

 

The best practice is that we should always use services with upper case “NodeService”, “ContentService”, “FileFolderService” etc. because this lower case “nodeService”, ”contentService” etc.  bypasses security check, audit and transaction checks.

Technically Alfresco uses AOP (Aspect-Oriented Programming) to expose services as “AOP proxies”.

 

 

To prove that, let’s go through publicly exposed services and some core services such as nodeService and contentService:

 

  1. https://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/COMMUNITYTAGS/V5.0.d/root/projects/repository/config/alfresco/public-services-context.xml
  2. https://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/COMMUNITYTAGS/V5.0.d/root/projects/repository/config/alfresco/content-services-context.xml
  3. https://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/COMMUNITYTAGS/V5.0.d/root/projects/repository/config/alfresco/node-services-context.xml

 

content-services-context.xml

<bean id="contentService" parent="baseContentService">

                <property name="store">

                   <ref bean="fileContentStore"/>

                </property>

</bean>

 

node-services-context.xml

<bean id="nodeService" class="org.springframework.aop.framework.ProxyFactoryBean">

                <!--  Lazy init to avoid circular dependencies  -->

                <property name="targetSource">

                                <bean class="org.alfresco.config.NonBlockingLazyInitTargetSource">

                                                <property name="targetBeanName">

                                                                <idref bean="_nodeService"/>

                                                </property>

                                </bean>

                </property>

                <property name="proxyInterfaces">

                                <list>

                                                <value>org.alfresco.service.cmr.repository.NodeService</value>

                                </list>

                </property>

</bean>


Note: you can look into the xml files for more details.

public-services-context.xml

<!--  Public Node Service  -->

<bean id="NodeService" class="org.springframework.aop.framework.ProxyFactoryBean">

                <property name="proxyInterfaces">

                                <list>

                                    <value>org.alfresco.service.cmr.repository.NodeService</value>

                                </list>

                </property>

                <property name="target">

                                <ref bean="nodeService"/>

                </property>

                <property name="interceptorNames">

                                <list>

                                                <idref local="NodeService_transaction"/>

                                                <idref local="AuditMethodInterceptor"/>

                                                <idref local="exceptionTranslator"/>

                                                <idref bean="NodeService_security"/>

                                                <idref bean="disableAuditablePolicySetPropertyInterceptor"/>

                                </list>

                </property>

</bean>

 

<!--  Public Content Service  -->

<bean id="ContentService" class="org.springframework.aop.framework.ProxyFactoryBean">

                <property name="proxyInterfaces">

                                <value>org.alfresco.service.cmr.repository.ContentService</value>

                </property>

                <property name="target">

                                <ref bean="contentService"/>

                </property>

                <property name="interceptorNames">

                                <list>

                                                <idref local="ContentService_transaction"/>

                                                <idref local="AuditMethodInterceptor"/>

                                                <idref local="exceptionTranslator"/>

                                                <idref bean="mlContentInterceptor"/>

                                                <idref bean="ContentService_security"/>

                                </list>

                </property>

</bean>

 

Notice that, the property ‘interceptorNames’ which has list of interceptors, which force the execution of transaction check (ContentService_transaction), audit (AuditMethodInterceptor) and security check (ContentService_security) etc.

 

So when you directly call the “contentService” (lower case), all these check are bypassed.

This can leads to security issue because alfresco will not evaluate the security before running the corresponding service operation in this case. So, it is best practice to use upper case services always.

Sometimes we need to perform some tasks or operations on regular basis or asynchronously after a certain time interval. It could be archival or transformation of contents or processing of jobs in Alfresco repository.

 

Another use case would be for example there is a third party system which is integrated with Alfresco and produces some output or transform contents for Alfresco, In this case you can’t rely on a synchronous call to the services on third party system. If the third party system gets hung or takes time to process the request your alfresco request will result into time out or failure. So in this case you can create job objects for each request into Alfresco inside some folder (job node) and let them processed by the job scheduler asynchronously. 

 

Job scheduler will fetch all jobs which are queued in job folder (job node) and process them. It keeps checking the status of the request placed at third party system every minute or 5 minute based on the scheduler configuration and do some operation once third party system returns desired response.

The simplest way would be to use Quartz framework with Cron. We will see how to create a simple job scheduler using the quartz and cron.

 

You can look the Chapter 22 in spring documentation and Quartz Enterprise documentation. Quartz and Cron are explained here.

 

I have used ‘org.quartz.StatefulJob’ to create a simple scheduled job in Alfresco. Let’s see how to implement it.

 

Before that you need to implement the Action/Webscript which will actually create job objects at some location (job folder) in alfresco repository. Scheduler will fetch these jobs and process them.

 

Let say there is an Action in alfresco using which user triggers transformation of a big XML document on a third party system.  

 

Assume following statements are in place in order to implement scheduled job:

 

Ø  Alfresco action invokes transformation on third party system

Ø  The third party system sends the response immediately with current transformation status say ‘Created’ and a unique id (e.g. transaction id).

Ø  Alfresco action reads the response and based on that it creates a content less job object in alfresco repository with the help of content model (content model defines an aspect and some properties to hold the transformation status, unique id, action requester user name/email, date, time, etc. ) and apply the properties to job object.

Ø  Scheduled job in Alfresco will fetch all job objects and keep checking the status of the transformation using the unique id (saved as part of job object) provided by third party system.

 

 

We will not go into much detail about how to create the job object. Above statements are more than enough for a developer to understand how to create job object, isn’t it J

 

Now it’s time to look at the actual topic which we are talking about here.

 

StatefulJob

 

Follow the below given steps to implement a job scheduler using org.quartz.StatefulJob:

 

1- Create custom-scheduled-job-context.xml where we define job detail and job trigger bean

 

<bean id="customJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="jobClass">

       <value>com.abhinav.jobs.CustomJobProcessor</value>

 </property>

 <property name="jobDataAsMap">

    <map>

 <entry key="serviceRegistry">

   <ref bean="ServiceRegistry"/>

 </entry>

 <entry key="globalProperties">

   <ref bean="global-properties"/>

 </entry>

   </map>

 </property>

 </bean>

 

Here, 

 

-- jobClass has a value ‘com.abhinav.jobs.CustomJobProcessor’. This class holds the actual job processing logic. 

 

-- The jobDataAsMap can be used to hold any number of (serializable) objects which you wish to have made available to the job instance when it executes. It is an implementation of the Java Map interface, and in jobDataAsMap you can pass alfresco services which will be required by your job to access repository.

 

-- org.springframework.scheduling.quartz.JobDetailBean 

 

It is subclass of Quartz' JobDetail class that eases bean-style usage. JobDetail itself is already a JavaBean but lacks sensible defaults.  

 

See more details here: JobDetailBean 

 

 

<bean id="customJobTrigger" class="org.alfresco.util.CronTriggerBean">

        <property name="jobDetail">

            <ref bean="customJobDetail"/>

        </property>

        <property name="scheduler">

            <ref bean="schedulerFactory"/>

        </property>

        <property name="cronExpression">

          <!-- Run the job every 1 minutes -->

          <value>0 0/1 * 1/1 * ? *</value>

        </property>

 </bean>

 

Here, 

 

-- jobDetail has a bean reference which we have defined above, it injects the job configuration defined above to org.alfresco.util.CronTriggerBean.

 

-- scheduler has a bean reference ‘schedulerFactory’, it injects the schedulerFactory (instance of org.quartz.Scheduler) to org.alfresco.util.CronTriggerBean

 

-- cronExpression value in customJobTrigger defines the time interval at which this job will be executed

 

-- org.alfresco.util.CronTriggerBean class is defined in Alfresco utility which extends ‘org.alfresco.util.AbstractTriggerBean’ and AbstractTriggerBean is the implementation of org.springframework.scheduling.quartz.JobDetailAwareTrigger’ 

 

 

See more details here:  CronTriggerBean

 

2- Import “custom-scheduled-job-context.xml” into module-context.xml if using amp, else place it inside <tomcat>/shared/classes/alfresco/extension

 

3- Now we have created custom Scheduled Job called customJobDetail and associating it with Job trigger called customJobTrigger.

 

4- Now, Create job class CustomJobProcessor  under package  com.abhinav.jobs and implements org.quartz.StatefulJob

 

5- We need to implement execute (signature given below) method and under that whole business logic will reside. 

 

public void execute(final JobExecutionContext jobCtx) throws JobExecutionException;

 

 

/*

* Author: Abhinav Kumar Mishra

* Copyright &COPY; 2015 Abhinav Kumar Mishra. All rights reserved.

*/

package com.abhinav.jobs;

 

import org.alfresco.repo.security.authentication.AuthenticationUtil;

import org.alfresco.service.ServiceRegistry;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

 

/**

 * The Class CustomJobProcessor.<br/>

 */

public class CustomJobProcessor implements org.quartz.StatefulJob{

/** The Constant LOG. */

private static final Log LOG = LogFactory.getLog(CustomJobProcessor.class);

    

/** The global properties. */

private Properties globalProperties;

/** The service registry. */

private ServiceRegistry serviceRegistry;

 

@Override

public void execute(final JobExecutionContext jobCtx) throws JobExecutionException {

   LOG.info("CustomJobProcessor Started..");

   try{

     //Run as system user since this job is user independent hence

       // permission is required on repo

    AuthenticationUtil.setRunAsUserSystem();

    // TODO:: Put job processing logic here..

    // Get the job space where all jobs are stored 

                  // using  serviceRegistry.getFileFolderService()

                  // Read the jobs in a list as given below:

                  // List<FileInfo> fileInfoList =    fileFolderService.listFiles(jobSpaceNode);

   // Read the uniuqe id from the job property and process

  } catch (RuntimeException excp){

     LOG.error("Exception occured while processing job", excp);

  }

  LOG.info("CustomJobProcessor End!");

}// execute end

 

/**

 * Sets the global properties.

 *

 * @param globalProperties the global properties

 */

public void setGlobalProperties(final Properties globalProperties) {

this.globalProperties = globalProperties;

}

/**

 * Sets the service registry.

 *

 * @param serviceRegistry the service registry

 */

public void setServiceRegistry(final ServiceRegistry serviceRegistry) {

this.serviceRegistry = serviceRegistry;

}

}

 

6- Prepare the amp and apply to alfresco.war or prepare the jar file and place it under <tomcat>/webapps/alfresco/WEB-INF/lib

 

7- Restart the server and your scheduled job will be executed on defined time intervals. You can define the cron expression based on your requirements. Refer this guide to know more about cron expressions and cron maker.

 

 

A cron expression is a string comprised of 6 or 7 fields separated by white space. Fields can contain any of the allowed values, along with various combinations of the allowed special characters for that field. The fields are as follows:

 

Field Name

Mandatory

Allowed Values

Allowed Special Characters

Seconds

YES

0-59

, - * /

Minutes

YES

0-59

, - * /

Hours

YES

0-23

, - * /

Day of month

YES

1-31

, - * ? / L W

Month

YES

1-12 or JAN-DEC

, - * /

Day of week

YES

1-7 or SUN-SAT

, - * ? / L #

Year

NO

empty, 1970-2099

, - * /



So cron expressions can be as simple as this: * * * * ? *

 

 

So, we saw that how to implement a simple scheduled job. But suppose that your alfresco server is running in Clustered environment (this is likely in production environment). In this case there will be multiple instances of the same schedulers running which is nothing but the multiple threads. Now to handle this case Alfresco has implemented ‘org.alfresco.schedule.AbstractScheduledLockedJob

 

It makes the cluster aware locking of the job transparent to the implementation. 

On the job's spring JobExecutionContext it will still always have to be passed as parameter the JobLockService.

 

The name to be used for locking of the job is optional, If none is passed a name will be composed using the simple name of the implementation class.

In general if it may make sense to have more than one job setup using the same class you should always use a different name on each JobExecutionContext to differentiate the jobs, unless you want the lock to be shared between the different instances.

 

AbstractScheduledLockedJob (Cluster Aware Job Scheduler Implementation)

 

Follow the below given steps to implement a job scheduler using org.alfresco.schedule.AbstractScheduledLockedJob:

 

1- Create custom-cluster-aware-scheduled-job-context.xml where we define job detail and job trigger bean

 

  <bean id="customClusterAwareJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">

 

 

<property name="jobClass">

<value>com.abhinav.jobs.CustomClusterAwareJobProcessor</value>

</property>

<property name="jobDataAsMap">

<map>

<entry key="fileFolderService">

  <ref bean="fileFolderService" />

</entry>

<entry key="nodeService">

   <ref bean="nodeService" />

</entry>

<entry key="transactionService">

 <ref bean="transactionService"/>

</entry>

<!-- JobLockService used to aquire the lock on jobs while they are 

     being processed to avoid other thread to modify the jobs state-->

<entry key="jobLockService">

<ref bean="jobLockService"/>

</entry>

</map>

</property>

</bean>

                       

  <bean id="customClusterAwareJobTrigger" class="org.alfresco.util.CronTriggerBean">

 

 

<property name="jobDetail">

<ref bean="customClusterAwareJobDetail" />

</property>

<property name="scheduler">

<ref bean="schedulerFactory" />

</property>

<property name="cronExpression">

    <!-- Provided the cron expession in alfresco-global.propeties file -->

<value>${customjob.cron.expression}</value>

</property>

</bean>

 

2- Import “custom-cluster-aware-scheduled-job-context.xml” into module-context.xml if using amp, else place it inside <tomcat>/shared/classes/alfresco/extension

 

3- Now we have created custom Scheduled Job called customClusterAwareJobDetail and associating it with Job trigger called customClusterAwareJobTrigger.

 

4- Now, Create job class CustomClusterAwareJobProcessor under package  com.abhinav.jobs and extend org.alfresco.schedule.AbstractScheduledLockedJob class.

 

5- We need to implement executeJob (signature given below) method and under that whole business logic will reside. 

 

public void executeJob(final JobExecutionContext jobCtx) throws JobExecutionException;

 

/*

* Author: Abhinav Kumar Mishra

* Copyright &COPY; 2015 Abhinav Kumar Mishra. All rights reserved.

*/

package com.abhinav.jobs;

 

import org.alfresco.repo.security.authentication.AuthenticationUtil;

import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;

import org.alfresco.schedule.AbstractScheduledLockedJob;

import org.alfresco.service.cmr.model.FileFolderService;

import org.alfresco.service.cmr.repository.NodeService;

import org.alfresco.service.transaction.TransactionService;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

 

/**

 * The Class CustomClusterAwareJobProcessor.<br/>

 * 

 * This class extends AbstractScheduledLockedJob to execute jobs using JobLockService. <br/>

 * It makes the cluster aware locking of the job transparent to the implementation. On the job's spring

 * JobExecutionContext it will still always have to be passed as parameter the

 * jobLockService.<br/> The name to be used for locking of the job is optional, if

 * none is passed a name will be composed using the simple name of the

 * implementation class. <br/>In general if it may make sense to have more than one

 * job setup using the same class you should always use a different name on each

 * JobExecutionContext to differentiate the jobs, unless you want the lock to be

 * shared between the different instances.<br/>

 * 

 * The only method to be implemented when extending this class is

 * executeJob(JobExecutionContext).

 * 

 * @see org.alfresco.schedule.AbstractScheduledLockedJob

 * @see org.alfresco.repo.lock.JobLockService

 */

public class CustomClusterAwareJobProcessor  extends AbstractScheduledLockedJob{

/** The Constant LOG. */

private static final Log LOG = LogFactory.getLog(CustomClusterAwareJobProcessor .class);

 

/** The file folder service. */

private FileFolderService fileFolderService;

 

/** The node service. */

private NodeService nodeService;

 

/** The transaction service. */

private TransactionService transactionService;

 

/**

 * This method will process the job by taking the lock on jobs. Since its an

 * extension of {@link org.alfresco.schedule.AbstractScheduledLockedJob} it

 * should also receive reference to the service

 * {@link org.alfresco.repo.lock.JobLockService}.

 *

 * @param jobContext the job execution context

 * @throws JobExecutionException if there is an exception while executing the job.

 */

@Override

public void executeJob(final JobExecutionContext jobCtx)

throws JobExecutionException {

LOG.info("CustomClusterAwareJobProcessor  Started..");

try {

// Run as system user since this job is user independent hence

// permission is required on repository

AuthenticationUtil.setRunAsUserSystem();

 

 

final RetryingTransactionCallback<Object> txnWork = 

                                                     new RetryingTransactionCallback<Object>() {

 

 

   public Object execute() throws Exception {

     // TODO:: Put job processing logic here..

      // Get the job space where all jobs are stored 

       // using  serviceRegistry.getFileFolderService()

      // Read the jobs in a list as given below:

       // List<FileInfo> fileInfoList = fileFolderService.listFiles(jobSpaceNode);

      // Read the uniuqe id from the job property and process

    return null;

}

     };

 transactionService.getRetryingTransactionHelper().doInTransaction(txnWork);

} catch (RuntimeException excp) {

LOG.error("Exception occured while processing job", excp);

}

 

 

LOG.info("CustomClusterAwareJobProcessor End!");

}

 

/**

 * Gets the file folder service.

 *

 * @return the file folder service

 */

public FileFolderService getFileFolderService() {

return fileFolderService;

}

 

/**

 * Sets the file folder service.

 *

 * @param fileFolderService the file folder service

 */

public void setFileFolderService(final FileFolderService fileFolderService) {

this.fileFolderService = fileFolderService;

}

 

/**

 * Gets the node service.

 *

 * @return the node service

 */

public NodeService getNodeService() {

return nodeService;

}

 

/**

 * Sets the node service.

 *

 * @param nodeService the node service

 */

public void setNodeService(final NodeService nodeService) {

this.nodeService = nodeService;

}

 

 

/**

 * Gets the transaction service.

 *

 * @return the transaction service

 */

public TransactionService getTransactionService() {

return transactionService;

}

 

 

/**

 * Sets the transaction service.

 *

 * @param transactionService the transaction service

 */

public void setTransactionService(final TransactionService transactionService) {

this.transactionService = transactionService;

}

}

 

6- Prepare the amp and apply to alfresco.war or prepare the jar file and place it under <tomcat>/webapps/alfresco/WEB-INF/lib

7- Restart the server and your scheduled job will be executed on defined time intervals.

The release of the following versions includes Mutual TLS Authentication by Default for Alfresco Repository and SOLR communications:

 

This blog post describes the changes required to upgrade your installation if you were using SSL or HTTP. Also a review on how the communication between SOLR and Repository works is included, as these concepts help to understand the simplified deployment diagrams with discouraged and recommended configurations.

 

Exec Summary

  • Alfresco is being released from now using Mutual Authentication TLS between Alfresco Repository and SOLR by default
  • Before upgrading from Alfresco 6.0, configuration properties in Repository and SOLR must be reviewed, as default values changed from none to https
  • Docker Compose templates and Helm Charts must be also reviewed, as when using plain HTTP a new ALFRESCO_SECURE_COMMS environment variable is required to configure Alfresco Search Services and Alfresco Insight Engine images
  • Out of the box truststores and keystores have been removed and they must be generated from scratch using the new Alfresco SSL Generator
    • If you were using custom truststores and keystores, you can keep on using them when upgrading
  • If you were using an unsecure deployment, you must switch to any of the recommended scenarios

 

Calls to Action

This section provides a guide to upgrade your installation from different use cases:

  1. You were using Mutual Authentication TLS (SSL)
  2. You were using HTTP and you want to employ SSL as part of the upgrade
  3. You were using HTTP and you want to continue using HTTP after the upgrade

 

1. If you were using Mutual Authentication TLS (SSL)

If you were using Mutual Authentication TLS protocol for these communications, everything should work as before when using these new releases.

 

However, from these versions, default keystores and truststores have been removed from distribution ZIP files and Docker Images.

 

  • Repository provided the cryptographic stores inside the classpath, as part of the alfresco-repository JAR library.
  • Search Services provided the cryptographic stores in the configuration folder for the instance templates, that is copied to alfresco and archive core when created.

 

Since these resources are not available out of the box anymore, it's required to create a new set of cryptographic stores before upgrading your server or to perform a backup of your previous stores and a restoring process in the upgraded server. A new tool, named Alfresco SSL Generator, has been developed for this purpose. This tool can be used from Windows, Linux and Mac computers and it produces all the cryptographic stores required to configure the Mutual Authentication TLS protocol.

$ cd ssl-tool

$ ./run.sh

$ tree keystores/
keystores/
├── alfresco
│ ├── keystore
│ ├── keystore-passwords.properties
│ ├── ssl-keystore-passwords.properties
│ ├── ssl-truststore-passwords.properties
│ ├── ssl.keystore
│ └── ssl.truststore
├── client
│ └── browser.p12
└── solr
├── ssl-keystore-passwords.properties
├── ssl-truststore-passwords.properties
├── ssl.repo.client.keystore
└── ssl.repo.client.truststore

 

Once the new stores have been created, you can copy them to Repository and SOLR configuration folders. Detailed steps for this process are available at:


If you were using the Docker Images, verify that your configuration is using the keystores and truststores from an external volume or extend default Docker Image to copy these cryptographic stores to the configured paths inside the container. A sample Docker Compose configuration is provided at Alfresco/alfresco-ssl-generator

 

Detailed information on how to use Alfresco Search Services Docker Image is available at Alfresco/SearchServices

 

2. If you were using HTTP and you want to switch to SSL

If you were using plain HTTP protocol for these communications but you want to use SSL after the upgrade, you must review your configuration to enable this protocol.

 

Repository

Inside alfresco-global.properties file, check that following values are set.

solr.host=localhost
solr.port.ssl=8984
solr.secureComms=https

 

Use Alfresco SSL Generator to produce all the cryptographic stores required to configure the Mutual Authentication TLS protocol.

 

Once the new stores have been created, you can copy them to Repository and SOLR configuration folders. Detailed steps for this process are available at Alfresco Content Services 6.1 > Installing the Alfresco WARS

 

Also Tomcat configuration for Repository needs to be reviewed, as a new SSL Connector for SOLR must be added. Detailed steps for this configuration are available at Alfresco Content Services 6.1 > Installing the Tomcat application server

 

SOLR

Inside solrcore.properties file (both "alfresco" and "archive" cores), following value must be set.

alfresco.secureComms=https

 

Additionally, Jetty server configuration and cryptographic stores paths must be set. Detailed steps for this configuration are available at Alfresco Search Services 1.3 > Installing and configuring Search Services with mutual TLS

 

If you were using the Docker Images, keystores and truststores must be set from an external volume or they should be copied to the configured paths inside the container. A sample Docker Compose configuration is provided at Alfresco/alfresco-ssl-generator

 

Detailed information on how to use Alfresco Search Services Docker Image is available at Alfresco/SearchServices

 

3. If you were using HTTP and you want to continue using HTTP

If you were using plain HTTP protocol for these communications, you must verify that you are explicitly using this configuration instead of relying on default values.

 

Repository

Inside alfresco-global.properties file, following value must be set.

solr.secureComms=none

If you were using the Docker Image, verify that this setting is also set in your service declaration in docker-compose.yml file.

alfresco:
image: alfresco/alfresco-content-repository:6.1.0.5
mem_limit: 1700m
environment:
JAVA_OPTS: "
-Ddb.driver=org.postgresql.Driver
-Ddb.username=alfresco
-Ddb.password=alfresco
-Ddb.url=jdbc:postgresql://postgres:5432/alfresco
-Dsolr.host=solr6
-Dsolr.port=8983
-Dsolr.base.url=/solr
-Dindex.subsystem.name=solr6
-Dsolr.secureComms=none
"

 

SOLR

Inside solrcore.properties file (both "alfresco" and "archive" cores), following value must be set.

alfresco.secureComms=none

If you are using the Docker Image, a new environment variable named ALFRESCO_SECURE_COMMS is available. This variable accepts "none" for HTTP and "https" for Mutual TLS Authentication, so you should add this line to your service declaration in docker-compose.yml file.

solr6:
    image: alfresco/alfresco-search-services:1.3.0.5
    environment:
        # Solr needs to know how to register itself with Alfresco
        - SOLR_ALFRESCO_HOST=alfresco
        - SOLR_ALFRESCO_PORT=8080
        # Alfresco needs to know how to call solr
        - SOLR_SOLR_HOST=solr6
        - SOLR_SOLR_PORT=8983
        # Create the default alfresco and archive cores
        - SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive
        - "SOLR_JAVA_MEM=-Xms2g -Xmx2g"
        # HTTP by default
        - ALFRESCO_SECURE_COMMS=none

 

Web Proxy

When using plain HTTP for communications between Repository and SOLR, some REST API services must be protected from external access, as only communications in the internal network are secure. Following snippet is a sample configuration for NGINX, but you can use any other Web Proxy for that.

# Protect access to SOLR APIs
location ~ ^(/.*/service/api/solr/.*)$ {return 403;}
location ~ ^(/.*/s/api/solr/.*)$ {return 403;}
location ~ ^(/.*/wcservice/api/solr/.*)$ {return 403;}
location ~ ^(/.*/wcs/api/solr/.*)$ {return 403;}
location ~ ^(/.*/proxy/alfresco/api/solr/.*)$ {return 403 ;}
location ~ ^(/.*/-default-/proxy/alfresco/api/.*)$ {return 403;}

Additional information for this task is available at Adding a reverse proxy in front of Alfresco Content Services.

 

We have included also this configuration as default for Docker Compose reference templates:

 

acs-deployment/docker-compose.yml at master · Alfresco/acs-deployment · GitHub 

Using alfresco/alfresco-acs-nginx:3.0.1 Docker Image for NGINX configuration.

 

acs-community-deployment/docker-compose.yml at master · Alfresco/acs-community-deployment · GitHub 

Using alfresco/acs-community-ngnix:1.0.0 Docker Image for NGINX configuration.

 

These are the main changes you'll find, but detailed information is provided below on why we did this change.

 

Technical information

 

In this section communication between Alfresco Repository and SOLR is revisited, in order to provide the right background to understand the discouraged and recommended deployment configurations.

 

Communication between Alfresco Repository and SOLR

 

The communication between Alfresco Repository and SOLR happens in both ways:

  • SOLR is polling Alfresco Repository for indexing (or tracking) the content, including changes in models, permissions, metadata and content. This polling is asynchronous and the frequency of these invocations can be scheduled using a cron expression named alfresco.cron in solrcore.properties file. By default, this communication happens every 10 seconds.

 

SOLR Indexing scheduled task

 

 

Searching from an end user application

Simplified Deployment Diagrams

Despite Alfresco can be deployed in many different ways, a simplified scenario can be described in the following diagrams.

 

Three alternatives are analysed:

  • SOLR and Alfresco communicating with HTTP, directly exposing services from Tomcat and Jetty application servers (insecure)
  • SOLR and Alfresco communicating with HTTP, exposing services via a Web Proxy (NGINX or equivalent) to protect external accesses to private REST API services (secure)
  • SOLR and Alfresco communicating with Mutual Authentication TLS, directly exposing services from Tomcat and Jetty application servers (secure)

 

SOLR and Alfresco communicating with HTTP (insecure)

Unprotected deployment for ACS using http

When using HTTP, SOLR REST API is exposed without authentication requirements. This means that SOLR can perform tracking operations with Repository but also any external application can use SOLR REST API to get information from repository (metadata, permission, models and content) without authentication.

 

The access to the SOLR Web Console is also exposed, at it's available by default at http://localhost:8983/solr without authentication.

 

In this scenario, Repository information is exposed, so you must avoid using this configuration.


SOLR and Alfresco communicating with HTTP and services protected by Web Proxy (secure)

 

ACS deployment protected by Web Proxy

 

When using HTTP behind a Web Proxy (like NGINX), SOLR REST API is exposed without authentication requirements internally. This means that SOLR can perform tracking operations with Repository using internal HTTP port 8080, but the external access to this API is protected by the Web Proxy. Any external application trying to access to SOLR REST API without authentication, using the external HTTP port 80, will be blocked by the rules described before at "If you were using HTTP > Web Proxy" section.

 

This approach is the one provided for Docker Compose and Helm Charts by default, as they are using a pre-configured Web Proxy to expose internal services.

 

The access to the SOLR Web Console, available by default at http://localhost:8983/solr, can be also exposed by the Web Proxy to be served at http://localhost/solr including a simple credential protection or any other mechanism to avoid public access.

 

NGINX sample configuration is provided below.

 

# SOLR Web Console
location /solr/ {

proxy_pass http://solr6:8983/;

# Basic authentication
auth_basic "Solr web console";
auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
}

 

In this scenario, Repository information is safely protected, so this configuration is recommended.

 

SOLR and Alfresco communicating with Mutual Authentication TLS (secure)

ACS deployment protected by Mutual Authentication TLS

When using Mutual Authentication TLS, SOLR REST API is exposed in SSL with clientAuth requirement. This means that SOLR can perform tracking operations with Repository using internal HTTPs port 8443 and signing the requests with the SOLR server certificate configured in solrcore.properties. Any external application trying to access to SOLR REST API using the HTTP port 8080, will be blocked by the Repository Web Filter. If the application uses the HTTPs port 8443, a clientAuth will be required and it will fail as the accepted server certificate is privately protected in SOLR server.

 

The access to the SOLR Web Console, available by default at https://localhost:8983/solr, is also protected by Mutual Authentication TLS. A client certificate, named browser.p12, is provided by the Alfresco SSL Generator in order to grant the access to this server.

 

In this scenario, Repository information is safely protected, so this configuration is recommended.

Filter Blog

By date: By tag: