Security and Authentication Refactor
From AlfrescoWiki
[edit] 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.
[edit] 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.
[edit] Authentication Overview
[edit] 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.
[edit] 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.
[edit] Implementation - Core Security Services
Please see Security Services for more information.
[edit] Providers
[edit] General
The person service knows nothing of home folder creation and providers. (This behaviour is bound to a create policy for the cm:person 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 cm:person 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.
[edit] 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>
[edit] 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).
[edit] Defining a home space during bootstrap
The BootstrapHomeFolderProvider class is present to support specifying home folders. You will probably not have to use this.
[edit] 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">
<!-- The name of the provider bean from the xml config -->
<!-- <value>companyHomeFolderProvider</value> -->
<!-- <value>guestHomeFolderProvider</value> -->
<!-- <value>personalHomeFolderProvider</value> -->
<!-- For the old behaviour this would be -->
<value>companyHomeFolderProvider</value>
</entry>
</map>
</property>
...
Insert non-formatted text here
[edit] 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.
[edit] Simple changes to the existing permissions model
[edit] 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"/>
[edit] Adding another role to the default list
[edit] How to add your own type or aspect, assign permissions for it, configure it and secure a service that uses it ....
[edit] 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.
<!-- ============================================== -->
<!-- Permissions associated with the Ownable aspect -->
<!-- ============================================== -->
<permissionSet type="cm:ownable" expose="selected">
<!-- Permission control to allow ownership of the node to be taken from others -->
<permissionGroup name="TakeOwnership" requiresType="false" expose="false"/>
<!-- The low level permission to control setting the owner of a node -->
<permission name="SetOwner" expose="false" requiresType="false">
<grantedToGroup permissionGroup="TakeOwnership" />
<!-- require to be able to reach the node and set properties in the node -->
<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.
[edit] The definition of this service
This can be found in public-services-context.xml.
<!-- Ownable Service -->
<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>
[edit] 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" />
[edit] 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.
[edit] 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.
...
<!-- =================================================== -->
<!-- Permission related to lock, check out and check in. -->
<!-- =================================================== -->
<permissionSet type="cm:lockable" expose="selected">
<!-- At the moment these permissions are hidden so they do not appear in the list -->
<!-- of permissions. -->
<!-- Check Out permission - exposed for all object types -->
<permissionGroup name="CheckOut" requiresType="false" expose="false"/>
<!-- Check In permission - only exposed when the lockable aspect is present -->
<permissionGroup name="CheckIn" requiresType="true" expose="false"/>
<!-- Cancel Check Out permission - only exposed for the lockable aspect is present -->
<permissionGroup name="CancelCheckOut" requiresType="true" expose="false"/>
<!-- Low level lock permission -->
<permission name="Lock" requiresType="false" expose="false">
<grantedToGroup permissionGroup="CheckOut" />
<requiredPermission on="node" type="sys:base" name="Write"/>
</permission>
<!-- Low level unlock permission -->
<permission name="Unlock" requiresType="true" expose="false">
<grantedToGroup permissionGroup="CheckIn" />
<grantedToGroup permissionGroup="CancelCheckOut" />
</permission>
</permissionSet>
...
...
<!-- Unlock is granted to the lock owner -->
<globalPermission permission="Unlock" authority="ROLE_LOCK_OWNER"/>
<!-- Check in is granted to the lock owner -->
<globalPermission permission="CheckIn" authority="ROLE_LOCK_OWNER"/>
<!-- Cancel check out is granted to the locak owner -->
<globalPermission permission="CancelCheckOut" authority="ROLE_LOCK_OWNER"/>
...
[edit] The Service Definition
....
<!-- COCI Service -->
<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>
...
[edit] The security configuration
...
<!-- ============================== -->
<!-- The Check-out/Check-in service -->
<!-- ============================== -->
<!-- To check out a node requires that you have permission to check out the node and -->
<!-- create the working copy in the specified location. Check in requires the -->
<!-- the associated permission, as does cancel check out. See the permission model -->
<!-- for how these permissions are granted. -->
<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>
...
[edit] 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)
[edit] 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.
[edit] 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);
}
}
}
[edit] 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'
);
- Note the MD4 hash for password 'admin' is
209c6174da490caeb422f3fa5a7ae634
- Note the MD4 hash for password 'test' is
0cb6948805f797bf2a82807973b89537
[edit] 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.
[edit] Implementation of Security
ACEGI has been chosen as the framework for implementing authentication and authorisation. Familiarity with the ACEGI reference documentation is assumed.
[edit] 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.
[edit] To Do
[edit] Support authentication against multiple authentication services
[edit] Short Term (1.3)
Add the idea of domain - but not yet used
[edit] 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
- Add attributes
- 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.
[edit] 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
[edit] 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.

