Obsolete Pages{{Obsolete}}
The official documentation is at: http://docs.alfresco.com
Authentication is carried out/required at several entry points to the repository, for example:
Authentication can be by an Alfresco ticket, a user name and password pair, or some other mechanism.
Using a ticket or username/password, a number of authentication instances may be tried for authentication, any one is sufficient.
Single sign on from the browser and via CIFS involves a multi-stage authentication process and may include negotiation of the authentication mechanism. There is no chaining of authentication for single sign on.
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 later.
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 config 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 includes the following functionality:
An authority is a string representation of a user name, group name, or role name.
Specific strings are used to identify well known authorities such as the administrator, everyone, owner and guest.
Prefixed strings are used to identify groups and roles.
Groups are identified by strings starting with the GROUP_ prefix, which should be hidden from end users in the client.
Similarly, roles start with the ROLE_ prefix.
Strings that do not match these patterns may be used for usernames.
The username is the key to obtain other information about a user such as their real identity, email etc.
This is held by the PersonService.
Some authorities are dynamic, such as the owner of a node and the owner of a lock held on a node.
These authorities are evaluated in the context of a node.
For some nodes you may be the 'owner' of that node. You will then have the permissions granted to the owner authority available to you.
Dynamic authorities are similar to an access policy. You can evaluate any condition knowing the node to be accessed and the current user and decide if the user should
have an authority in the given context. An access policy would be a dynamic permission assignment.
Groups can include both other groups and users so it is possible to construct hierarchies.
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.
By default, the owner of a node is the person who created it. Ownership may be transferred to another user.
Only one user can own a node. However, other users may be able to take ownership of a node if they have suitable permission.
Permissions are identified by a string. A particular permission, for example 'ReadChildren', may be granted or denied to an authority; a user, group, administrator, owner, etc. The children of a node will inherit permissions from their parents. So by default, the files in a folder will inherit their permissions from the folder. Permissions set on a node take precedence over permissions set on the parent nodes. The inheritance of permissions may be turned off for any node. A permission group is a convenient grouping of permissions such as 'Read' made up of 'ReadProperties' and 'ReadChildren'.
As the username is used as the key to find personal information, group membership and against which to assign permission, it is key to know if this is case sensitive or not.
The security framework as a whole may be case sensitive, or not.
Configuration of this key behaviour is set in repository.properties. It is quite unusual to have case sensitive user ids.
An ACL is a permission or permission group assigned to an authority for a particular node. An ACL can deny or allow. An 'allow' permission will be inherited by any children unless the permission is denied by an ACL on a child or inheritance of ACLs is disabled on a child. Allow takes precedence. If 'bob' is a member of the group 'rats' and 'bob' is allowed 'read' and 'rats' is denied 'read' then 'bob' will be allowed read. Any allow allows access, as opposed to a single deny denies all.
Global Permission - a permission or permission group assigned to an authority regardless of the node. A global permission takes precedence over node specific ACLS. If 'bob' were granted the global permission 'read' then 'bob' would have the 'read' permission for everything, regardless of any ACLs set on any nodes.
We think of role is an idea in the UI that refers to a set of permissions that can be applied to a folder. If you have role based permissions, this is not usually the same thing; it is really just another authority, like groups, with global permissions.
How are you using roles?
The diagram below shows some simple permissions that have applied to a basic directory strutcure. There are two users, andy and dave, to which permissions have been assigned. Permissions are also assigned to two special authorities: one that applies to all users and one that applies to the owner of a node. In this example, we are only interested in read, create and all permissions.
A summary of the permissions shown in the above diagram:
/app:company_home/app:andy
/app:company_home/app:dave
/app:company_home/app:andy/appublic
Security is enforced when methods are called on public services.
Access control is defined in meta-data and not in code.
The meta-data is used to 'inject' security in an aspect oriented way.
Security settings can therefore be changed without affecting the implementation code.
Security could be based on:
Security is supported by five key services:
The authentication service provides an API to:
Not all implementations will support creating, updating and deleting authentication information.
The authenticated username is used as the key to obtain other security information such as group membership,
the details about the person, to record a user as the owner of an object.
It is one of the identifiers against which permissions may be assigned.
The authentication service does not provide any details about a user other than authentication.
The authentication service stores authentication information on the calling thread.
Application developers should ensure that this information is cleared.
The authentiation service brings together three components:
The authentication component supports authentication only. The authentication DAO provides an API to create, delete and update authentication information. The ticket component is resposible for managing and storing tickets that may be obtained after authentication and used in place of authentication.
The implementation and configuration for this service can be found in authentication-services-context.xml.
The product ships with a default authentication implementation. This default implementation coordinates two service providers for the AuthenticationComponent and MutableAuthenticationDAO. It also uses the permission service provider interface to clear up permissions as users are deleted. Tickets are supported using the ticket component.
The only configuration option that should be changed is if user names are case sensitive. This is set in repository.properties.
Note that in the authentication service implementation, the MD4 hash algorithm is used to encode passwords.
This is to allow the open source authentication service to support authentication using NTLM while using the CIFS protocol.
Alfresco ships with a DAO provider implementation to plug into the Acegi DAO provider, this supports authentication using information held in the repository. There is a provider to authenticate credentials that have programmatically been set as authenticated. The authentication service uses this provider to set the current user (without Acegi attempting to authenticate against the DAO). An NTLM authentication implementation is also available.
Alfresco does not add roles and groups etc. to the objects held in the Acegi context. This information is encapsulated through the AuthorityService. If you use Acegi already, you could implement an authority service that retrieves group and role information from the Acegi authentication object.
Alternative implementations of the authentication service and its supporting components for various flavours of external authentication systems, including NTLM, LDAP, JAAS, and Kerberos, have been included in the preconfigured Authentication Subsystems. Multiple instance of these can be 'chained' together to support more advanced enterprise-level authentication requirements. See Alfresco Authentication Subsystems for more information.
The authority service is responsible for:
It does not support users, authentication and user management. This is for the AuthenticationService.
The authority service is defined and configured in authority-services-context.xml.
This file includes documentation on how to configure specific implementations of this service.
You can provide your own implementation of this service by implementing the
org.alfresco.service.cmr.security.AuthorityService interface. The implementation should be a spring bean identified by the id authorityService. This will then get wrapped with the appropriate transactional and security behaviour and exposed as a service bean called AuthorityService.
The default authority service implementation uses Alfresco Repository nodes to store authority metadata. These nodes are under the path /sys:system/sys:authorities in the spaces store.
Note: whilst these nodes under /sys:system/sys:authorities are accessible through other Alfresco APIs, e.g. Javascript and REST APIs, they should be treated as read only and should not be modified through any other APIs other than the authority service. Otherwise authority information may be lost on an upgrade.
A common configuration requirement is to change the set of user names that the authority service automatically recognizes as administrator authorities. For example, if using pass-through authentication with a Windows domain server, you might want to add 'administrator' to this set of names.
This is now all configurable through the authentication subsystem without any need to edit spring configuration. See Alfresco Authentication Subsystems for more details.
The idea of file ownership is present in both unix and windows. The repository has the concept of node ownership. This is optional and is implemented as an aspect. The owner of a node may have specific ACLs granted to them. Owner is a dynamic authority. If the Ownable aspect is not present, the creator is used as the default owner. The owner is simply defined using the user name. If the username of the current user matches the user name of the node owner then the current user will be granted all permissions assigned to the owner authority.
The ownable service is responsible for:
The Ownable Service is supported by an ownable aspect defined in contentModel.xml.
There are a set of well known permissions associated with the Ownable Service.
The beans and configuration for the ownership service are defined in ownable-services-context.xml. There are no configuration options for this service.
The permission service is responsible for:
The PermissionService interface defines constants for well known permissions and authorities.
The default implementation coordinates implementations of two service provider interfaces; a ModelDAO and a PermissionsDAO. A permission is just a name scoped by the fully qualified name of the type or aspect to which it applies. The beans are defined and configured in public-services-security-context.xml. This file also contains the configuration for security enforcement.
The ModelDAO interface defines an API to access a permissions model. This model defines low level permissions, groups of permissions, and groups of permissions that extend existing groups of permissions. Each permission or grouping of permissions applies only in the context of a type or aspect, or to all types. Permission requirements may then be set in service beans to protect method invocations. For example, a method may only be called by authenticated users that have been granted particular permissions on one of the nodes identified by a parameter for the method. The model also supports static or global permissions. Global permissions apply to all objects in the repository and take precedence over permissions assigned to individual nodes. For example, the assignment of all permissions to the admin user for all nodes in the repository. The permission model is separate from the data dictionary; fully qualified type names provide the key to link the two together.
The PermissionsDAO interface defines an API to persist permissions set against specific nodes and to determine if the current user has a specified permission for a given node. This takes into account both global and node level permissions and all the authorities that apply to the current user.
In the default PermissionsDAO implementation, a permission or permission group is granted or denied to an authority for a particular node. There can be any number of these entries for a given node. Permissions will be inherited by any children unless the permission is denied by an ACL on a child or inheritance of ACLs is disabled on a child. An ACL can deny or allow. Allow takes precedence. If 'bob' is a member of the group 'rats' and 'bob' is allowed 'read' and 'rats' is denied 'read' then 'bob' will be allowed read. Any allow allows access, as opposed to Microsoft file systems, wherein a single deny denies all.
Permissions are assigned at the node level, not at the attribute level. Ths makes sense with the current implementation of search.
Search results need to reflect what the user performing the search can see. It makes sense that all properties have the same read access as the node, as nodes are indexed for searching. Applying read ACLs at the property level would require a change to the indexing implementation or a complex post analysis to work out how nodes were found by the search.
If not, The values of properties could be deduced by how a readable node was found from a search on restricted properties.
Fine grain attribute permissions could be implemented by using children nodes to partition meta-data if required.
If we need to support attribute level permissions in the future, the best compromise would be to index as we are now but only allow different read access for properties that are not indexed - so their content can not be determined via query. Alternatively, only index properties etc. that have the same access permissions as the node. In both cases the content can not be determined from the index. We could have additional indexing support to search against restricted properties.
What is a role? You could define groups and roles, against which you could assign permissions and permission groups, by convention, starting with 'ROLE_' for roles and 'GROUP_' for groups. The AuthorityService is responsible for providing the additional authorities, over and above user name, that apply to a particular user. This service can be extended or replaced if you want to add additional roles/groups to users. We do not use roles in this way, as there is not much distinction between a role and a group.
A role is more like a set of permissions that are granted to a particular authority for all nodes. Or if it is context sensitive, a set of permissions granted to a particular authority for a given node and maybe its children. Both of these are supported by the implementation
The available permissions are defined in the permissions model. This is defined in the permissionDefinitions.xml file in the repository/config/aflresco/model directory. This configuration is loaded in a bean definition in the public-services-security-context.xml file. This file also defines global permissions. The definition file is read once at application start up. If you make changes to this file you will have to restart the repository to apply the changes.
Here is the v1.0 permission model definition as an example PermissionModelDefintionExample
The person service is the API by which nodes of the person type, as defined in contentModel.xml, should be accessed.
The person service is responsible for:
The beans to support the person service and its configuration can be found in authentication-services-context.xml. The principle configuration options are around how people are created on demand if users are managed via NTLM or some other external user repository.
If you are using an external user repository it is likely that you will have to implement your own person service using the org.alfresco.service.cmr.security.PersonService interface. If not, you will want people to be created in the repository on demand so they do not have to exist before a user logs in.
How do I end up with duplicate person objects?
Duplicate person entries can now be handled in a number of ways.
This can be configured on the person service in authentication-services-context.xml.
TODO: include the existance of preferences and personal configuration in selecting the best person against which to match for duplicate users.
Since 1.4, a personal home space (or folder) may be created for each person. Prior to 1.4 their home spaces would be a shared Company Home Space. It is still possible to share home spaces but this is no longer the default.
When a person's home space is created, the person property cm:homeFolderProvider (the name of the provider) is used by the HomeFolderManager to obtain the provider. The provider locates or creates the home folder, but normally does this by specifying the location and calling a method on the HomeFolderManager. The location of the home folder is saved in the person property cm:homeFolder.
Custom providers may be created however there are a number of predefined providers:
The creation of the home folder may be delayed until first used. The PersonService has a homeFolderCreationEager property to indicate if the home folder should be created when the person object is created.
The HomeFolderManager has a defaultProvider property that specifies the default provider to be used if there is no homeFolderProvider property on the person, making this pluggable (see the bean in authentication-services-context.xml). The default provider for LDAP import is defined in common-ldap-context.xml, but the cm:homeFolderProvider property may also be set from an attribute stored in LDAP.
Some interfaces, such as Alfresco Explorer create the home folder themselves and set the cm:homeFolder property when creating the person. As a result the userHomesHomeFolderProvider discovers the folder already exists so does not try to create it, but will still set permissions.
The HomeFolderProvider2 interface providers methods to obtain a number of properties that define where the home folder should be located or created.
Prior to 4.0, home folder providers implemented HomeFolderProvider which simply has OnCreateNodePolicy and getName methods. This has now been deprecated in favour of HomeFolderProvider2 which has methods to obtain properties common to most providers. This allows much of the code in the providers to be moved into the HomeFolderManger.
Custom providers that implement the deprecated HomeFolderProvider interface still work, but are limited when it comes to moving home folders and ongoing support as the interface will be removed at some point in the future. Custom providers that extend the now deprecated AbstractHomeFolderProvider will fully support moving home folders as the abstract class has been modified to use an adaptor between the two interfaces.
The largeHomeFolderProvider is needed to split home folders into groups so they don't all exist under the same rootPath. Having thousands of folders under one folder will cause performance problems. This provider may be configured to introduce zero or more parent folders above the actual home folder. For example we might wish to group users if they share the same two characters of their user name:
al/alan
al/alfred
ja/jane
The largeHomeFolderProvider uses the RegexHomeFolderProvider class. It is an implementation that returns a tree structure for a home folder based on a property (typically userName) from the supplied person. The parent folder names are derived from regular expression groups matched against the property value. The final folder name is the full property value.
The above example could be achieved using the regular expression '^(..)'. With the regular expression '^(.)(.?)' the home folder path would be
a/l/alan
a/l/alfred
j/a/jane
If any group matches a zero length string, it is just ignored.
The largeHomeFolderProvider has the following additional properties, which are set from Alfresco global properties (in brackets), so are easily customised.
As of 4.0, it is possible to move home folders to their preferred provider location or even override the provider to be used. This is achieved by setting a number of Alfresco global properties and restarting Alfresco. The home folders are moved during the start-up process which may take some time. The global properties should be cleared once Alfresco is up, to avoid repeating the process the next time Alfresco starts.
This action may be required if a large number of users have been created via an LDAP import directly under the rootPath or if a new tree structure for home folders has changed and there are user that use a previous structure.
The home folders of internal users (such as admin and guest) are not moved, nor are users that share home folders (the rootPath of the provider).
It is also possible change the HomeFolderProvider used by all other users by setting the global property home_folder_provider_synchronizer.override_provider=<providerBeanName>.
By default no action is taken unless the the global property home_folder_provider_synchronizer.enabled=true
Warning: The LDAP synchronise process overwrites the home folder provider property. This is not an issue as long as the rootPath of the overwriting provider is the same as the overwritten provider or is not an ancestor of any of the existing home folders. This is important because the rootPath value (or the original provider) is used to tidy up empty 'parent' folders under the rootPath when a home folders are moved elsewhere. If you have any concerns that this may not be true, set the global property home_folder_provider_synchronizer.keep_empty_parents=true and tidy up any empty folders manually. Historically users created by the LDAP sync process are all placed under the same root folder so there will be no parent folders anyway.
The following global properties:
spaces.user_homes.regex.pattern=^(..)
home_folder_provider_synchronizer.enabled=true
home_folder_provider_synchronizer.override_provider=largeHomeFolderProvider
Security is enforced around public services. Web services, the UI, CIFS, WebDav, FTP etc all use public services and so include security enforcement. Public services are defined in public-services-context.xml.
Access control allows or prevents people from executing service methods on a particular object by checking if the current user, or any of the authorities granted to the current user, has a particular permission or permission group.
For example, on the NodeService bean, the readProperties method checks that the current user has read access to the properties of the node before invoking the method. On the SearchService, the results from queries are restricted to return only the nodes for which a user has read permission.
Security is enforced by defining proxies for each internal service implementation and adding. A method interceptor to enforce security is included in each proxy. When a method is called on a public service, the security interceptor is called before it returns execution to the internal implementation. At this stage, the interceptor can examine the function arguments to the method and check that the user has the appropriate rights for each in order to invoke the method.
For example, a method delete(NodeRef nodeRef) exists on the node service. The security interceptor can see the nodeRef argument before the underlying delete(...) method is called.
If configured correctly, the interceptor could check that the current user has 'Delete' permission for the node. If not, a security exception is raised. If permission allows the method goes ahead.
In a similar manner, after a method has executed the interceptor can examine the returned object and decided if it should return it the caller. For example, a search method could return a list of nodes. The security interceptor could filter this list for only those nodes that the current user can 'Read'.
It is possible you may want to configure a method so that it can only be called by an admin user, specific users, groups of users etc. Ths can also be enforced by the security method interceptor.
Access control for public services is definied in public-services-context.xml. The public services are the only Spring beans to have access restrictions.
Each service defines a method interceptor bean that enforces any security requirements for method calls. By convention, these are defined in public-services-security-context.xml, along with any other supporting beans. This file also defines the location from which the permission model is loaded.
Here is the v1.0 public services security context as an example PublicServicesSecurityContext
In public-services-security-context.xml the beans required to support acegi based security around method invocation are defined.
This configures two alfresco specific beans: a voter that can veto method execution based on the permissions granted to the current user for specifc arguments to the method, and an after invocation provider to apply security to objects returned by methods. Method access defined in the normal acegi way with a few additions.
In the followng: ? represents an authority (user name or group), # a method argument index and * a string representation of a permission.
Pre-conditions take one of the following forms:
If more than one ACL_NODE.#.* or ACL_PARENT.#.* entry is present then all the permissions must be available for the method to execute.
Post-conditions take the forms:
The post-conditions will create access denied exceptions for return types like NodeRef's, StoreRef's, ChildAssociationRef's. For collections and arrays members will be filtered based on the access conditions.
Some examples and explanations taken from the method security interceptor for the NodeService public service bean.
Refer to the NodeService interface for the method documentation and methos parameters etc.
org.alfresco.service.cmr.repository.NodeService.getStores=AFTER_ACL_NODE.sys:base.Read
If the returned object is a collection or array then filter its members to contain only those that can be read by the current user.
If it is a single reference to a node of some form, throw an AccessDeniedException.
org.alfresco.service.cmr.repository.NodeService.createStore=ACL_METHOD.ROLE_ADMINISTRATOR
Allow access to users who are members of the administrator group.
(This does not use an acegi ROLE_... as we do not bind groups and roles to the acegi authentication object.
org.alfresco.service.cmr.repository.NodeService.exists=ACL_NODE.0.sys:base.Read
Only allow for users who have read permission for the node specified by the first parameter of the method.
org.alfresco.service.cmr.repository.NodeService.createNode=ACL_NODE.0.sys:base.CreateChildren
Only allow for users who have create children permission for the node specified by the first parameter of the method.
org.alfresco.service.cmr.repository.NodeService.moveNode=ACL_NODE.0.sys:base.WriteProperties,ACL_PARENT.0.sys:base.DeleteChildren,ACL_NODE.1.sys:base.CreateChildren
Only users who can write the properties of the moving node, remove the node from its parent, and ann children to the destination node can perform a move node.
org.alfresco.service.cmr.repository.NodeService.setChildAssociationIndex=ACL_PARENT.0.sys:base.WriteProperties
Setting the index of an association requires the write properties permissions
org.alfresco.service.cmr.repository.NodeService.getType=ACL_NODE.0.sys:base.ReadProperties
Getting the type of a node required read permission for properties.
org.alfresco.service.cmr.repository.NodeService.addAspect=ACL_NODE.0.sys:base.Write
Adding an aspect to a node requires the write permission set.
org.alfresco.service.cmr.repository.NodeService.removeAspect=ACL_NODE.0.sys:base.Write
Remoing an aspect from a node requires the write permission set.
org.alfresco.service.cmr.repository.NodeService.hasAspect=ACL_NODE.0.sys:base.ReadProperties
Querying if a node has an aspect requires the read properties permission on the node.
org.alfresco.service.cmr.repository.NodeService.getAspects=ACL_NODE.0.sys:base.ReadProperties
Getting a list of all aspects for a node requires the read properties permission on the node.
org.alfresco.service.cmr.repository.NodeService.deleteNode=ACL_NODE.0.sys:base.Delete
Deleting a node requires the delete permission. Testing for delete on the children is not possible for performance reasons.
The node service should retest this for each node that it tries to delete.
org.alfresco.service.cmr.repository.NodeService.addChild=ACL_NODE.0.sys:base.CreateChildren,ACL_NODE.1.sys:base.ReadProperties
Adding children to a node requires the create children property on the parent and read properties on the node (this ensures that you can see the child node)
org.alfresco.service.cmr.repository.NodeService.removeChild=ACL_NODE.1.sys:base.Delete
To remove a child node you need delete permission for the child (this includes the test to see if it is deletable from the parent.)
org.alfresco.service.cmr.repository.NodeService.getProperties=ACL_NODE.0.sys:base.ReadProperties
Accessing the properties of a node requires the permission to read properties.
org.alfresco.service.cmr.repository.NodeService.getProperty=ACL_NODE.0.sys:base.ReadProperties
Accessing a particular property on a node requires the permission to read properties.
org.alfresco.service.cmr.repository.NodeService.setProperties=ACL_NODE.0.sys:base.WriteProperties
Setting all properties on a node required the permission to write properties.
org.alfresco.service.cmr.repository.NodeService.setProperty=ACL_NODE.0.sys:base.WriteProperties
Setting a specific property on a node required the permission to write properties.
org.alfresco.service.cmr.repository.NodeService.getParentAssocs=ACL_NODE.0.sys:base.ReadProperties,AFTER_ACL_PARENT.sys:base.Read
Getting the parent associations on a node requires read access to the node. The method only reports the parents that are visible.
There may be mutiple parents some of which are not visible. You must have access via the primary parent to read a node.
org.alfresco.service.cmr.repository.NodeService.getChildAssocs=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read
Reading child associations requires read access to the node and filters for only the children that are visible.
org.alfresco.service.cmr.repository.NodeService.getPrimaryParent=ACL_NODE.0.sys:base.ReadProperties,AFTER_ACL_PARENT.sys:base.Read
Accessing the primary parent requires that the node and its primary parent are accessible. In future, we may allow access along a link and not just the primary parent, in which case it is possible that the primary parent is not visible.
org.alfresco.service.cmr.repository.NodeService.createAssociation=ROLE_AUTHENTICATED
org.alfresco.service.cmr.repository.NodeService.removeAssociation=ROLE_AUTHENTICATED
org.alfresco.service.cmr.repository.NodeService.getTargetAssocs=ROLE_AUTHENTICATED
org.alfresco.service.cmr.repository.NodeService.getSourceAssocs=ROLE_AUTHENTICATED
Currently, there are no retrictions on the creation of associations between nodes.
org.alfresco.service.cmr.repository.NodeService.getPath=ACL_NODE.0.sys:base.ReadProperties
Getting the primary path to a node requires read access to the node.
org.alfresco.service.cmr.repository.NodeService.getPaths=ACL_NODE.0.sys:base.ReadProperties
Getting all the paths to a node requires read access to the node.
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-context.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.
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'/>
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 'TakeOwnership' 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.
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>
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' />
Now would be a good time to look at the rest of the permission model config to see what it does.
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 owner 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'/>
...
....
<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>
...
...
<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>
...
First, consider the following questions.
Unless you are reimplementing a full security framework (including permissions) you should only have to:
You should not have to implement:
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.
The following class will allow the generation of the correct MD4 hash.
You will need the following jars:
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);
}
}
}
If you do not know the admin password it can be reset several ways.
Note: in the text below when reading MD4 hash of a password you need to understand MD4 hash of the UTF16LE encoded password (i.e. the NTLM hash). Here is a python sample code:
import hashlib
passwd = 'admin'
# NTLM (as in alfresco)
print hashlib.new('md4', passwd.encode('utf-16le')).hexdigest()
# 209c6174da490caeb422f3fa5a7ae634
# simple hash
print hashlib.new('md4', passwd).hexdigest()
# f9d4049dd6a4dc35d40e5265954b2a46
For an Alfresco 3.1sp1 or later system, use the following instructions:
1) Run the following commands and check you have only one row in the output:
SELECT anp1.node_id,
anp1.qname_id,
anp1.string_value
FROM alf_node_properties anp1
INNER JOIN alf_qname aq1 ON aq1.id = anp1.qname_id
INNER JOIN alf_node_properties anp2 ON anp2.node_id = anp1.node_id
INNER JOIN alf_qname aq2 ON aq2.id = anp2.qname_id
WHERE aq1.local_name = 'password'
AND aq2.local_name = 'username'
AND anp2.string_value = 'admin'
It should output the current md4 hashed password for the 'admin' user.
An example output is:
+---------+----------+----------------------------------+
| node_id | qname_id | string_value |
+---------+----------+----------------------------------+
| 4 | 10 | 209c6174da490caeb422f3fa5a7ae634 |
+---------+----------+----------------------------------+
1 row in set (0.00 sec)
2) If you get only one row, then update the table:
UPDATE alf_node_properties
SET string_value='209c6174da490caeb422f3fa5a7ae634'
WHERE
node_id=THENODEIDABOVE
and
qname_id=THEQNAMEVALUEABOVE
Where you need to replace THENODEIDABOVE and THEQNAMEVALUEABOVE with the results from step 1), in this example 4 and 10 respectively.
Note: Please use caution when running this SQL, and ensure that you have the appropriate AND conditions in the UPDATE query.
3) Restart Alfresco.
Note: The hashed password you use in your UPDATE statement MUST be in all lower case. If you use a hash tool that returns a string with uppercase letters (as many internet NTLM hashing tools do), change them all to lowercase.
For Alfresco 1.4, use the following instructions:
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'
);
209c6174da490caeb422f3fa5a7ae634
0cb6948805f797bf2a82807973b89537
For Alfresco version 1.3, use the following instructions:
You can use this to get the hash for current admin password:
select string_value
from alf_node_properties p, alf_namespace ns, alf_qname q
where p.qname_id = q.id
and q.ns_id = ns.id
and ns.uri = 'http://www.alfresco.org/model/user/1.0'
and q.local_name = 'password'
AND node_id in (
SELECT node_id
FROM alf_node_properties p, alf_namespace ns, alf_qname q
WHERE p.qname_id = q.id
and q.ns_id = ns.id
and ns.uri = 'http://www.alfresco.org/model/user/1.0'
and q.local_name = 'username'
AND string_value = 'admin'
);
And, you can use this to set the password:
UPDATE alf_node_properties p
SET string_value = '<MD4 hash here>'
WHERE p.qname_id = (select q.id from alf_qname q, alf_namespace ns
where q.ns_id = ns.id
and ns.uri = 'http://www.alfresco.org/model/user/1.0'
and q.local_name = 'password')
AND node_id in (
SELECT node_id
FROM alf_node_properties p, alf_namespace ns, alf_qname q
WHERE p.qname_id = q.id
and q.ns_id = ns.id
and ns.uri = 'http://www.alfresco.org/model/user/1.0'
and q.local_name = 'username'
AND string_value = 'admin'
);
For Alfresco versions lower than 1.3, set the password in the database using the MD4 hash. For example:
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'
);
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.
ACEGI has been chosen as the framework for implementing authentication and authorisation.
Familiarity with the ACEGI reference documentation is assumed.
Configuration instruction for the enterprise networks can be found at Alfresco Authentication Subsystems. This inlcudes JAAS, LDAP and Kerberos configuration.
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.
The default permissions model defines the following permissions.
From 1.4, previously permission groups were not prefixed by _ and there were no simple permission groups so that more complex permission groups were easy to build.
For those with RBAC experience, you may think of stuff starting with _ as a permission and the rest as roles. However subjects (people, groups and dynamic subjects) can be assigned to either. The checks around service can also use any. So it is not traditional RBAC.
The underlying permissions are defined here. These are:
These are grouped into simple permission groups, which themselves can be grouped into more complex groups (see cmbject)
Simple groups are normally used to control the access to methods on public services.
This is clear as they translate by convention to a single permission.
There are some common groupings of permissions for CRUD operations on nodes.
As cmbject but with no Administrator permission group.
These apply to all nodes regardless of where they are in the repository.