2. Why ?
• All apps require some user right management.
• Creating permission management is boring and may be tedious.
• A basic implementation always need refactoring after new
modules are added to an application.
• I'm lazy and prefer to use a proved model and limit code
required in a new app.
• I do not like having to rewrite non app specific code all the time.
3. Some history
• Basic principles (Roles and Lists) used since 2003 in PHP apps.
• First implementation of the model in PHP in 2006 for a central
user management system used by many php apps.
• The model can manage all private apps I worked on.
• First WebObjects implementation in 2011.
• Complete rewrite in 2013 for easier integration.
4. Do / don't do
• Manage user access rights and include a permission editor UI
component.
• Manage component access rights with annotation.
• Support territory or other data segmentation needs with list
associated to role.
• Do not provide authentication but includes password hashing
classes you may use.
• Probably too complex for a public accessible site or app.
5. Definition: List
• List are used to segment data or access right.
• List are usually linked to another entity of the application like
territory, division, warehouse, department, ...
• A list contains items; a territory list may contain north America,
south America, Europe and Asia for example.
• Items are used to specify with part of the data is relevant for a
user; a manager for the north and south Americas for example.
6. Definition: Role
• Role are the base permission unit.
• Used to define what a user can do; a user has roles.
• Used to define who can access a component; a component is
accessible by user with a specified role.
• Role can be qualified by item from list for data segmentation; a
salesman role is qualified by territory item(s). used to define
watch a user can do
7. Definition: Profile
• A profile define a user type; administrator is a common user
profile.
• A profile is defined by a set of included roles and a set of
optional roles.
• Profile are used to simplify the user right management. By
selecting a profile for a user, the set of options is limited to
relevant roles.
8. Definition: User Profile
• A user profile is a complete user access right definition with:
• A profile with it's included roles and selected role's items,
• A set of optional roles selected with their role's items.
• A user can have multiple user profiles but only one is effective
and one is marked as default (selected when the user log in).
• Multiple user profiles are used for peoples requiring complex
permission with incompatible data segmentation like a
warehouse manager also a sale manager.
9. UML model
Managed by the
UserPermissionEditor component
or your own permission editor.
Managed by a plist file specified in
properties or manually.
Updated automatically if the list is linked to an entity.
You have to create this entity.id
passwordHash
app specific attributes...
Real user entity
id
roleGroup_id
code
displayOrder
permissionList_id
allowsMultipleItems
KARole
id
code
KAProfile
id
displayOrder
code
KARoleGroup
*
* **
*
id
code
KAAccessList
userProfileRole_id
permissionListItem_id
KAUserProfileRoleItem
id
userProfile_id
role_id
KAUserProfileRole
id
user_id
profile_id
isDefaultProfile
KAUserProfile
profile_id
role_id
isOptionnal
KAProfileRole
*
*
*
*
id
accessList_id
code
KAAccessListItem
*
*
id
passwordHash
KAUser
10. Using it in a project
• Add the framework to build path.
• Create the real user entity in your model by selecting the KAUser as parent class.
• Implements the createHomePageForUserProfile(...) method in your user class.
• Create the foreign key constraint on KAUserProfile in your migration code.
• Create the role.plist file.
• Add some properties.
• Create your user management components using the provided UserPermissionsEditor.
• Total time: about 15 minutes for a working skeleton.
12. Framework Properties
• ka.accesscontrol.rolesFile : the name of the plist file with your
role and profile definition.
• ka.accesscontrol.rolesFileBundle : the name of the framework
containing your roles file. Leave blank if the file is in app bundle.
• ka.accesscontrol.loggedOutPageName : the name of the
component to return when a user log out.
• er.migration.migrateAtStartup=true
• er.migration.modelNames=KAAccessControl,yourModels
13. In your framework or application Properties file, add:
#Path to the plist file read at startup to updates the frameworks data tables AccessList, RoleGroup, Role and Profile.
ka.accesscontrol.rolesFile=Roles
#Name of the framework that contain the plist file, not needed if the file is inside the app bundle.
ka.accesscontrol.rolesFileBundle=FrameworkName
!
#Name of the WOComponent to return when a user log out of the system.
ka.accesscontrol.logedOutPageName=LoggedOut
!
# Migrations
er.migration.migrateAtStartup=true
er.migration.createTablesIfNecessary=true
er.migration.modelNames=KAAccessControl,YourModelNames...
!
#Make sure you put KAAccessControl before your migration if you want to
# create the foreign key constraint or some bootstrap users.
14. Some useful addition to your migration class
To create the foreign key constrain in the database, add this line in your migration class:
!
KAAccessControlMigrationHelper.addForeignKeyAndIndex(database, "YourRealUserTableName");
!
Make sure the Roles are up to date before creating the first user
RolesFileLoader.loadRolesFile(editingContext);
editingContext.saveChanges();
!
#Bootstrap an admin user.
#Suppose your real user class is User...
User admin = ERXEOControlUtilities.createAndInsertObject(editingContext, User.class);
admin.changePassword("adminPassword");
admin.defaultUserProfile().setProfileWithCode("Admin");
!
#Set other mandatory attributes in your User entity...
admin.setLanguage(User.englishLanguageCode);
admin.setUsername("admin");
!
16. The User class
// This required method create the home page based on the UserProfile selected.
!
@Override
public WOComponent createHomePageForUserProfile(WOContext context, KAUserProfile userProfile) {
if (userProfile.profileCode().equals(Profile.Admin)) {
return ERXApplication.application().pageWithName("UserList", context);
}
if (userProfile.profileCode().equals(Profile.WharehouseManager)) {
return ERXApplication.application().pageWithName("WarehouseDashboard", context);
}
return ERXApplication.application().pageWithName("CustomerList", context);
}
17. The List, Profile and Role classes
• These classes are not mandatory but highly recommended.
• Put the list, profile and role code in string constants.
• Use these contants for annotations or permission checking in
the code.
• Using constants helps to reduce typing errors.
• Eclipse has a nice function to find all references to a constant.
18. Localization
• Profile name in key "profile.profileCode"
• Role name in key "role.roleCode"
• RoleGroup name in key "group.groupCode"
• Few strings found in the framework Localizable.strings
19. ListItemAutoUpdater
• Link a list to an entity using a Java annotation.
• @AutoSyncWithAccessList(listCode="ListCode",
nameProperty=EntityClass.NAME_KEY)
• List item copy the name returned by the key specified in the
annotation as displayed name on the UI.
• Retrieve selected items as EOs from a user profile with
itemsAsObjectsForRole(eoClass, roleCode).
• The system autostart in the framework didFinishInitialization().
20. Auto synced entity
@AutoSyncWithAccessList(listCode=List.Territory, nameProperty=SaleTerritory.LOCALIZED_NAME_KEY)
@SuppressWarnings("serial")
public class SaleTerritory extends com.kaviju.accesscontroldemo.model.base._SaleTerritory {
@SuppressWarnings("unused")
private static Logger log = Logger.getLogger(SaleTerritory.class);
!
static public final String LOCALIZED_NAME_KEY = "localizedName";
static public final ERXKey<String> LOCALIZED_NAME = new ERXKey<String>(LOCALIZED_NAME_KEY);
!
public String localizedName() {
if (ERXLocalizer.currentLocalizer().languageCode().equalsIgnoreCase(User.frenchLanguageCode)) {
return nom();
}
return name();
}
}
21. UserAccessControlService
• Responsible to manage the current userProfile and the personify
stack.
• Personify allow a user to log as another user, very useful for
technical support.
• There is a real and current user (usually the same) like in UNIX.
• The session create the service and pass a delegate for logon
events; usually itself.
• The delegate receive userProfileDidLogon(userProfile) to
prepare or clean the session for the new active profile.
22. Session.java
public class Session extends ERXSession implements UserLogonDelegate{
private UserAccessControlService<User> userAccessService;
!
public Session() {
userAccessService = new UserAccessControlService<User>(this);
setTimeOut(10*60);
...
}
!
@Override
public void userProfileDidLogon(KAUserProfile userProfile) {
setLocalizerWithUserLanguage(userProfile.user(User.class));
setTimeOut(4*3600);
if (Profile.ShortSession.equals(userProfile.profileCode())) {
setTimeOut(15*60);
}
}
!
public void setLocalizerWithUserLanguage(User user) {
if (User.frenchLanguageCode.equals(user.language()) ) {
setLanguage("French");
shortDateFormatter = new ERXJodaLocalDateFormatter("d MMMM", Locale.CANADA_FRENCH, null);
}
if (User.englishLanguageCode.equals(user.language()) ) {
setLanguage("English");
shortDateFormatter = new ERXJodaLocalDateFormatter("MMMM d", Locale.CANADA, null);
23. The logon method
public WOActionResults logon() {
// Fetch the User with your custom attributes.
User user = User.fetchUser(session().defaultEditingContext(), User.USERNAME.eq(username()));
!
if (user != null && password != null) {
// Use the PasswordHasher to verify the password and upgrade it to new hasher if required.
if (user.someAthenticationMethod(password)) {
// Tell the UserAccessControl someone logged in and return the provided home page.
return session().userAccessService().logonAsUser(user);
}
}
displayError = true;
return null;
}
25. BaseComponent.java
@Override
protected void checkAccess() throws SecurityException {
if (context().page().equals(this)) {
ComponentAccessService accessService = ComponentAccessService.getInstance();
if ( accessService.isComponentAccessibleForUserProfile(getClass(), currentUserProfile()) == false) {
throw new SecurityException("Component "+getClass().getSimpleName()+" require one of these roles "+
accessService.readAllowedForRolesAnnotationInClass(getClass())+
" current user "+currentUser()+" have these "+currentUserProfile().allEffectiveRoles());
}
}
super.checkAccess();
}
!
public KAUserProfile currentUserProfile() {
return session().userAccessService().currentUserProfile();
}
!
public User currentUser() {
return session().userAccessService().currentUser();
}
public boolean isUserAdmin() {
String currentUserProfileCode = session().userAccessService().currentUserProfile().profileCode();
return currentUserProfileCode.equals(Profile.Admin);
}
26. Using PasswordHasher
• Currently the framework contains Pbkdf2Hasher and
LatinSimpleMD5Hasher.
• You can add your Hasher by creating a subclass of
PasswordHasher.
• Register knows hasher(s) during application init.
• Saved hash contains a hasher identifier params and salt.
• Set the default hasher to use when changing a user password
with KAUser.setCurrentPasswordHasher(hasher).
27. Using legacy password hashes
• Register a default hasher to use with incomplete hashes using
KAUser.setDefaultPasswordHasher(hasher).
• The default hasher is used when the current hash for user does
not contains an hasher code.
• authenticateWithPasswordAndUpgradeHashIfRequired(password)
will upgrade the hash with the current hasher if the password is
verified.
• This method will upgrade any verified hash to the current hasher.
28. Next features ?
• New password hashers.
• Support for LDAP authentication.
• UI to edit the roles.plist file.
• Adjustments for WOInject.