Security and Authentication Refactor

cancel
Showing results for 
Search instead for 
Did you mean: 

Security and Authentication Refactor

resplin
Intermediate
0 0 3,372

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com



OverviewSecurity


Table of Contents


Introduction


Any discussion of security addresses two issues – Authentication and Authorization.  Authentication asks the question is whether a user is valid and should be allowed to use the system.  Authorization then allows to specify what an authenticated user can do.   An authenticated user may have no permissions granted to him.


Authentication


When any call is made to the repository through the public service API, the caller must first be authenticated. This can be done by logging in using a username and password or using a ticket. A ticket can be requested after logging in and can be used, under certain conditions, to revalidate a user. Some applications and authentication mechanisms may support single sign on.

Authentication is managed by the core repository.  Since the repository has many interfaces, this means that uthentication is carried out/required at all entry points to repository:


  • CIFS
  • Web Client
  • FTP
  • WebDAV
  • Web Services
  • Spring Beans exposed as public services in Java
  • Web Scripts



Authentication can be by an Alfresco ticket, a user name / password pair, or some other mechanism.

Given a ticket or username/password, a number of authentication authorities may be tried for authentication.  The user needs to be authenticated by at least one authority,

Single sign on from the browser and via CIFS involves a multi-stage authentication process and may include negotiation of the authentication mechanism.  While authentication chaining is supported in other modes, there is no authentication chaining for single sign on.




Authentication Overview


1008px




Notes


The authentication DAO and authentication component implementation are paired in a single instance of an Authentication Service Impl.
The third element is the ticket component. See Please see below.

The architecture could be unified by supporting complex callbacks from the AuthenticationService API.
This would make the implementation parallel JAAS. So why not use JAAS completely? The configuration of JAAS via configuration files does not play well with the configuration of beans within the Spring framework. The call back would have to include service injection. This is going to give repeated configuration parameters and fun with things like hibernate persistence.

The ticket component should be added to the diagram.

MD4 password hashes live at the bottom of the NTLM and NTLM2 authentication protocols. This is a fundamental restriction as to when NTLM can be used.


Security Overview


Security includes the following functionality:


  • users and user management;
  • provision of personal information about users;
  • user authentication;
  • groups and group management;
  • ownership of nodes within the repository;
  • repository wide permissions;
  • permissions at the node level;
  • an extendable permission model; and
  • access control, to restrict calls to public services to suitable authenticated users.




Implementation - Core Security Services


Please see Security Services for more information. 


Providers


General

The person service knows nothing of home folder creation and providers.
(This behaviour is bound to a create policy for the cmSmiley Tongueerson type using a bean exposing the HomeFolderManager class.)
HomeFolderManager defines the default home folder provider.

The base class used to implement the providers below allows permissions to be set. There are two sets of permissions: those set when home spaces are created and those set when a home space is assigned (it already existed).

Common properties for all providers:


name
The bean name is the name of the home space provider. Set cm:homeSpaceProvider on cmSmiley Tongueerson to this value to select the use of a particular provider.

homeFolderManager
Inject the home folder manager.

storeRef
A store ref (used for search context etc - to look up path)

serviceRegistry
Inject the service registry.

path
A path to a folder. This is required in some form by all providers. They may use this path in different ways.

ownerOnCreate
The name of the owner to set on creation. If not specified then the person will own their home space. So you could set this to 'admin' if you do not want folk to own their home spaces.

inheritsPermissionsOnCreate
If a home space is created, set if permissions are inherited. The default is false.

ownerPemissionsToSetOnCreate
A set of permissions to set for the owner when a home space is created.

permissionsToSetOnCreate
A Map of Sets of permissions to for specified users when a home space is created

userPemissions
The permissions to set for the user/person when a homespace is created or an existing one reused.

clearExistingPermissionsOnCreate
Specifically to support clearing permissions when a home folder is created from a template. If true then permissions are cleared, if false they will be taken from the template.

Defining a home space that is an existing space (ExistingPathBasedHomeFolderProvider)

path
The path to folder to assign as the home space.



The default in 1.3 and before was to use company home.
This can be implemented using the provider below.





    <bean name='companyHomeFolderProvider' class='org.alfresco.repo.security.person.ExistingPathBasedHomeFolderProvider'>
        <property name='serviceRegistry'>
           <ref bean='ServiceRegistry' />
        </property>
        <property name='path'>
           <value>/${spaces.company_home.childname}</value>
        </property>
        <property name='storeUrl'>
           <value>${spaces.store}</value>
        </property>
        <property name='homeFolderManager'>
    <ref bean='homeFolderManager' />
</property>
    </bean>
   

    <bean name='guestHomeFolderProvider' class='org.alfresco.repo.security.person.ExistingPathBasedHomeFolderProvider'>
        <property name='serviceRegistry'>
           <ref bean='ServiceRegistry' />
</property>
        <property name='path'>
           <value>/${spaces.company_home.childname}/${spaces.guest_home.childname}</value>
        </property>
        <property name='storeUrl'>
           <value>${spaces.store}</value>
        </property>
        <property name='homeFolderManager'>
    <ref bean='homeFolderManager' />
</property>
        <property name='userPemissions'>
           <set>
              <value>Consumer</value>
           </set>
        </property>
    </bean>




Creating a home space based on uid

Below is an example configuration for creating a home folder based on the uid of a user.
As the uid is unique it can be used to name a home folder.



Properties:


templatePath
An optional path to a template node used to create home spaces.

path
The folder in which to create home spaces.





    <bean name='exampleHomeFolderProvider' class='org.alfresco.repo.security.person.UIDBasedHomeFolderProvider'>
        <property name='serviceRegistry'>
           <ref bean='ServiceRegistry' />
        </property>
        <property name='path'>
           <value>/${spaces.company_home.childname}</value>
        </property>
        <property name='storeUrl'>
           <value>${spaces.store}</value>
        </property>
        <property name='homeFolderManager'>
           <ref bean='homeFolderManager' />
        </property>
        <property name='ownerOnCreate'>
            <value>admin</value>
        </property>
        <property name='inheritsPermissionsOnCreate'>
            <value>false</value>
        </property>
        <property name='ownerPemissionsToSetOnCreate'>
            <set>
                <value>Coordinator</value>
                <value>All</value>
            </set>
        </property>
        <property name='permissionsToSetOnCreate'>
            <map>
                <entry key='GROUP_A'>
                    <set>
                        <value>Consumer</value>
                    </set>
                </entry>
                <entry key='GROUP_B'>
                    <set>
                        <value>Editor</value>
                        <value>Collaborator</value>
                    </set>
                </entry>
            </map>
        </property>
        <property name='userPemissions'>
            <set>
                <value>All</value>
            </set>
        </property>
        <property name='templatePath'>
           <value>/${spaces.company_home.childname}/${spaces.guest_home.childname}</value>
        </property>
        <property name='clearExistingPermissionsOnCreate'>
            <value>true</value>
        </property>
    </bean>



Also refer to authentication-services-context.xml for examples of 'userHomesHomeFolderProvider' (the current default provider, which creates home folders under User Homes) and 'personalHomeFolderProvider' (which creates home folders under Company Home).


Defining a home space during bootstrap

The BootstrapHomeFolderProvider class is present to support specifying home folders.
You will probably not have to use this.


LDAP Import

The LDAP import can specify a default home folder provider name, or it could be set per person imported by setting the cm:homeFolderProvider property from an attribute stored in LDAP.

To set the default provider for imported LDAP users that do not specify a specific home folder provider



...
<property name='attributeDefaults'>
            <map>
                <entry key='cm:homeFolderProvider'>




                    <value>companyHomeFolderProvider</value>
                </entry>
            </map>
</property>
...

Insert non-formatted text here




The default permission model and simple extensions


The default permission model is defined in config/alfresco/model/permissionDefinitions.xml according to config/alfresco/model/permissionSchema.dtd, which describes the elements of the model and how they should be used.

The file that defines the permission model is defined in public-services-security-modex.xml in the permissionsModelDAO bean.

To extend or change the permission model:

1) In the extensions directory, over ride this bean to point to a file containing the  complete permission definitions

   <bean id='permissionsModelDAO' class='org.alfresco.repo.security.permissions.impl.model.PermissionModel'>
       <property name='model'>
           <value>alfresco/extension/myPermissionDefinitions.xml</value>
       </property>
       <property name='nodeService'>
           <ref bean='nodeService' />
       </property>
       <property name='dictionaryService'>
           <ref bean='dictionaryService' />
       </property>
   </bean>

2) Update the permissions definitions as required.


Simple changes to the existing permissions model


Removing all permissions from OWNER of a file


This may have side effects, removing write permission where it is required (e.g. to add shortcuts to the person object). This means all users will require explicit permissions to be set for them.

<globalPermission permission='Read' authority='ROLE_OWNER'/>




Adding another role to the default list


How to add your own type or aspect, assign permissions for it, configure it and secure a service that uses it ....


The Ownable Permissions Model


We will take implementing the ownable aspect as an example.

Assuming the ownable aspect has been defined in a model, we can link it into the permission model xml definition.





  
   <permissionSet type='cm:ownable' expose='selected'>
     
      <permissionGroup name='TakeOwnership' requiresType='false' expose='false'/>
     
      <permission name='SetOwner' expose='false' requiresType='false'>
        <grantedToGroup permissionGroup='TakeOwnership' />
        <requiredPermission on='parent' name='ReadChildren' />
        <requiredPermission on='node' name='WriteProperties' />
      </permission>
     
   </permissionSet>



This configuration entry defines a set of permissions related to the ownable aspect. The set definition says that only some of  permissions defined here should be reported in the list of permissions that can be set on a node. As opposed to saying all are exposed.



It defines one low level permission 'SetOwner'. The expose option determines if this low level permission should be exposed to an end user; in this case not. The requires type implies that this permission can be set for any object type, not just those that are of a derived type or have an aspect of the derived type.  This permission is added to the TakeOwnership permission group. The permission requires some other permissions to be granted to the user. That they can access the node, and all of its parents 'ReadChildren' and because owership is held as a node property, to change this you need the 'WriteProperties' permission.



The 'TakeOnwership' permission group again can be used for any type, and is not exposed in the UI.



Effectively this support is all hidden at the moment.




The definition of this service


This can be found in public-services-context.xml.



   
    <bean id='OwnableService' class='org.springframework.aop.framework.ProxyFactoryBean'>
     <property name='proxyInterfaces'>
      <value>org.alfresco.service.cmr.security.OwnableService</value>
     </property>
     <property name='target'><ref bean='ownableService'/></property>
     <property name='interceptorNames'>
      <list>
          <idref local='OwnableService_transaction' />
                <idref local='exceptionTranslator' />
                <idref bean='OwnableService_security' />
       <idref local='OwnableService_descriptor' />
      </list>
     </property>
    </bean>

    <bean id='OwnableService_transaction' class='org.springframework.transaction.interceptor.TransactionInterceptor'>
        <property name='transactionManager'>
            <ref bean='transactionManager'/>
        </property>
        <property name='transactionAttributes'>
            <props>
                <prop key='*'>${server.transaction.mode.default}</prop>
            </props>
        </property>
    </bean>
   
    <bean id='OwnableService_descriptor' parent='AlfrescoServiceDescriptor'>
     <property name='interface'>
      <value>org.alfresco.service.cmr.security.OwnableService</value>
     </property>
     <property name='description'>
      <value>OwnableService Service</value>
     </property>
    </bean>




Security Configuration for this service


This definition refers to the OwnableService_security bean. This is defined in public-services-security-context.xml.
This definition would place no restrictions on this service.



<bean id='OwnableService_security' class='org.alfresco.repo.security.permissions.impl.AlwaysProceedMethodInterceptor' />






The Lockable permissions model and Check Out/Check In


Now would be a good time to look at the rest of the permission model config to see what it does.




The lockable permission model


Lock and Check Out/Check In permissions are defined against the lockable aspect.

The permission model entries for the lockable service and the global permissions that allow the ower of a lock
to check in, unlock, and cancel a check out.



...


  
   <permissionSet type='cm:lockable' expose='selected'>
   

   
      <permissionGroup name='CheckOut' requiresType='false' expose='false'/>
     
      <permissionGroup name='CheckIn' requiresType='true' expose='false'/>
     
      <permissionGroup name='CancelCheckOut' requiresType='true' expose='false'/>
   
      <permission name='Lock' requiresType='false' expose='false'>
        <grantedToGroup permissionGroup='CheckOut' />
        <requiredPermission on='node' type='sys:base'  name='Write'/>
      </permission>
     
      <permission name='Unlock' requiresType='true' expose='false'>
        <grantedToGroup permissionGroup='CheckIn' />
        <grantedToGroup permissionGroup='CancelCheckOut' />
      </permission>     
     
   </permissionSet>
...
...
   <globalPermission permission='Unlock' authority='ROLE_LOCK_OWNER'/>
  
   <globalPermission permission='CheckIn' authority='ROLE_LOCK_OWNER'/>
  
   <globalPermission permission='CancelCheckOut' authority='ROLE_LOCK_OWNER'/>
...

The Service Definition



....

    <bean id='CheckoutCheckinService' class='org.springframework.aop.framework.ProxyFactoryBean'>
     <property name='proxyInterfaces'>
      <value>org.alfresco.service.cmr.coci.CheckOutCheckInService</value>
     </property>
     <property name='target'><ref bean='checkOutCheckInService'/></property>
     <property name='interceptorNames'>
      <list>
          <idref local='CheckoutCheckinService_transaction' />
                <idref local='exceptionTranslator' />
                <idref bean='CheckoutCheckinService_security' />
       <idref local='CheckoutCheckinService_descriptor' />
      </list>
     </property>
    </bean>

    <bean id='CheckoutCheckinService_transaction' class='org.springframework.transaction.interceptor.TransactionInterceptor'>
        <property name='transactionManager'>
            <ref bean='transactionManager'/>
        </property>
        <property name='transactionAttributes'>
            <props>
                <prop key='*'>${server.transaction.mode.default}</prop>
            </props>
        </property>
    </bean>

    <bean id='CheckoutCheckinService_descriptor' parent='AlfrescoServiceDescriptor'>
     <property name='interface'>
      <value>org.alfresco.service.cmr.coci.CheckOutCheckInService</value>
     </property>
     <property name='description'>
      <value>Version Service</value>
     </property>
    </bean>
...




The security configuration



...


   



   
    <bean id='CheckoutCheckinService_security' class='net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor'>
        <property name='authenticationManager'><ref bean='authenticationManager'/></property>
        <property name='accessDecisionManager'><ref local='accessDecisionManager'/></property>
        <property name='afterInvocationManager'><ref local='afterInvocationManager'/></property>
        <property name='objectDefinitionSource'>
            <value>
                org.alfresco.service.cmr.coci.CheckOutCheckInService.checkout=ACL_NODE.0.cm:lockable.CheckOut,ACL_NODE.1.sys:base.CreateChildren
                org.alfresco.service.cmr.coci.CheckOutCheckInService.checkin=ACL_NODE.0.cm:lockable.CheckIn
                org.alfresco.service.cmr.coci.CheckOutCheckInService.cancelCheckout=ACL_NODE.0.cm:lockable.CancelCheckOut
            </value>
        </property>
    </bean>
...

Implementing your own authentication mechanism


First, consider the following questions.


  • Does your authentication system support single sign on?

    If so, you will need to work out how this interacts with the web browser, and may be a portal container. On the client side, you will need to write and configure an Authentication Filter. The NTLM authentication filter provides a good example, as does the NovellIChainsHTTPRequestAuthenticationFilter. The latter can be used with SiteMainder and can be used to integrate with CAS. TODO: improve configuration support for this filter.




  • Does your authentication API support recovering an MD4 password hash?

    This is a requirement to integrate your authentication implementation with the NTLM authentication mechanism used by CIFS. If an MD4 password is not available you will not be able to use your authentication system to provide access to CIFS using NTLM authentication. CIFS can go direct to a Kerberos server. Configuration options for CIFS can be found:




  • Does your authentication mechanism expose adding and deleting users?

    If your authentication system is read only, you only need to implement the AuthenticationComponent interface and wire this implementation into the AuthenticationService. You will need  the DefaultMutableAuthenticationDAO implementation, which does not allow any changes to authentication information.




  • Do you manage groups (or roles)?

    If yes, you will need to implement the AuthorityService interface to support groups, an provide support to get lists of all users and groups etc.




  • Do you manage personal information?

    If yes, you will need to implement a PersonService that creates personal information in the alfresco repository. You will probably want to configure how it creates default spaces etc. Approaches may be: to create People on demand, load all people on app start up, synchronise with a back end store as queries are made.   




  • Does your authentication mechanism support case sensitive user names?

    You need to set the configuration in repository.properties and use this property to configure any services you implement to be sensitive to the case of user names.




  • Are you integrating with an existing authentication API or storing authentication information yourself?

    If the authentication mechamism exists, implement the AuthenticationComponent interface be extending AbstractAuthenticationComponent. If you allow user manipulation, also implement the MutableAuthenticationDAO. If you are storing the information yourself, consider using the default implementation, or use this as a model for constructing your own.



Unless you are reimplementing a full security framework (including permissions) you should only have to:


  • implement an AuthenticationComponent by extending AbstractAuthenticationComponent and wire this into the AuthenticationService in the Spring configuration;
  • implement a MutableAuthenticationDAO, or use the default one, and wire this into the AuthenticationService in the Spring configuration;
  • implement a PersonService and configure it in Spring;
  • implement an AuthorityService and configure it in Spring.



You should not have to implement:


  • the OwnableService; or
  • the PermissionService (unless you want another permissions model and permission dao)

The Admin password in the default authentication


Ths admin password for the default authentication is set as part of the initial bootstrap.
This is located in config\alfresco\bootstrap\alfrescoUserStore.xml. The password is MD4 encoded as required by NTLM. The encoding is important.


How to generate the correct MD4 hash


The following class will allow the generation of the correct MD4 hash.

You will need the following jars:


  • cryptix-jce-provider.jar
  • commons-codec-1.2.jar



public class MD4HashGenerator 
{

   static
   {
       try
       {
           MessageDigest.getInstance('MD4');
       }
       catch (NoSuchAlgorithmException e)
       {
           Security.addProvider(new CryptixCrypto());
       }
   }

   public MD4HashGenerator()
   {
       super();
   }

   /**
    * @param args
    */
   public static void main(String[] args)
   {

       System.out.println('Hash: ' + new String(Hex.encodeHex(md4(args[0]))));

   }

   private static byte[] md4(String input)
   {
       try
       {
           MessageDigest digester = MessageDigest.getInstance('MD4');
           return digester.digest(input.getBytes('UnicodeLittleUnmarked'));
       }
       catch (NoSuchAlgorithmException e)
       {
           throw new RuntimeException(e.getMessage(), e);
       }
       catch (UnsupportedEncodingException e)
       {
           throw new RuntimeException(e.getMessage(), e);
       }
   }

}

How to reset the admin password


If you do not know the admin password it can be reset several ways.


  • If you know the password of at least one user
    • Give a known user admin rights in config\alfresco\authority-services-context.xml
    • Login in as this user
    • Reset the admin password
    • Reset the config




  • If you do not know the admin password
    • Configure the authenticatoin component to accept all logins using  org.alfresco.repo.security.authentication.SimpleAcceptOrRejectAllAuthenticationComponentImpl
    • Login as anyone who has admin rights
    • Reset the password
    • Revert the configuratoin



For alfresco 1.3 and lower, set the password in the database using the MD4 hash, like this.



UPDATE node_properties
SET string_value = '<MD4 hash here>'
WHERE qname = '{http://www.alfresco.org/model/user/1.0}password'
AND guid = (
    SELECT guid
    FROM node_properties
    WHERE qname = '{http://www.alfresco.org/model/user/1.0}username'
    AND string_value = 'admin'
);

In alfresco 1.4 you will have to do this.



UPDATE alf_node_properties
SET string_value = '<MD4 hash here>'
WHERE qname = '{http://www.alfresco.org/model/user/1.0}password'
AND node_id in (
   SELECT node_id
   FROM alf_node_properties
   WHERE qname = '{http://www.alfresco.org/model/user/1.0}username'
   AND string_value = 'admin'
);

  1. Note the MD4 hash for password 'admin' is
    209c6174da490caeb422f3fa5a7ae634

  2. Note the MD4 hash for password 'test' is
    0cb6948805f797bf2a82807973b89537

Guest


Guest uses the lowercase userName 'guest'. If you delete the guest user you should be able to recreate them again with this user id and using the guest home as their home space. It does not matter what first name or surname etc they are given.

It will also not matter what password they are given. The bootstrap does not usually create a guest user, only the guest person is required.


Implementation of Security


ACEGI has been chosen as the framework for implementing authentication and authorisation.
Familiarity with the ACEGI reference documentation is assumed.


Enterprise Network authentication configuration


Configuration instruction for the enterprise networks can be found at Enterprise Security and Authentication Configuration. This inlcudes JAAS, LDAP and Kerberos configuration.






To Do


Support authentication against multiple authentication services


Short Term  (1.3)


Add the idea of domain - but not yet used


Medium Term (1.4)


Move Groups from the user store into the spaces store


  • Groups of people not users
    • Add attributes
      • Add domain to users and people in the model
      • gid
      • name
  • Fix group service
  • Patch to move groups and add default name
  • Update LDAP sync of groups including group name
  • Approx 3 days



Introduce the concept of domains.



Authentication Services


  • Each authentication service applies to a domain
  • There may be one or more authentication service in a domain.
  • When there is more than one authentication service in a domain they are handled as the simple chaining case, as described above.



Domain


  • Users, groups and people all apply to a domain.
  • Assign permissions to groups and people
    • Domain + id
    • Node Ref (could assign permissions to the person and group noderefs)
    • Patch
    • Requires groups to be moved
    • Tickets will have to identify a user and domain

Domain as a string

Both microsoft and unix do not allow '\' in user names. In Windows it is used as the domain name\user name separator. It seems reasonable that we can represent domain and user name in the same way. If there is no '\' in the name then there is no domain present implying that the default domain should be used. This significantly reduces the collateral damage of adding domain.



Impact of adding domains


  • UI to manage groups, people and users
  • LDAP import to specific domain
  • UI - Login
  • UI Permissions
  • Domain+User as a string authority vs object - still should be a unique person or group



Approx. 4 days work

Also requires 0.5 days to add a username constraint to exclude '\' in user names.


Longer Term


  • permissions and visibility of groups and users to other groups and users
  • visibilty of people based on group membership. This would hace to be done in the service layer (not via the node service)
  • Admin could be domain specific - repo and UI support






Mapping of external roles, groups and users


All usernames and authentication tokens will be mapped to a unique repository identifier.
If external usernames or authentications are changed these can then be updated in the mapping and have no effect in information (e.g. permissions) stored in the repository.

Authorisation will be defined against these internal representations.

In most cases this will be an identity mapping unless the authentication already exists, its name has been changed or there is some reason to map many external roles into one internal role etc.

These translations will set up during authentication to support existing ACEGI code.






ACLs


Access Control Lists


Default Permissions


Default Permissions Model Reference