29
KAAccessControl A framework for easy user access control. Samuel Pelletier

KAAccessControl

Embed Size (px)

Citation preview

Page 1: KAAccessControl

KAAccessControlA framework for easy user access control. Samuel Pelletier

Page 2: KAAccessControl

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.

Page 3: KAAccessControl

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.

Page 4: KAAccessControl

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.

Page 5: KAAccessControl

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.

Page 6: KAAccessControl

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

Page 7: KAAccessControl

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.

Page 8: KAAccessControl

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.

Page 9: KAAccessControl

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.idpasswordHashapp specific attributes...

Real user entity

idroleGroup_idcodedisplayOrderpermissionList_idallowsMultipleItems

KARole

idcode

KAProfile

iddisplayOrdercode

KARoleGroup

*

* **

*

idcode

KAAccessList

userProfileRole_idpermissionListItem_id

KAUserProfileRoleItem

iduserProfile_idrole_id

KAUserProfileRoleiduser_idprofile_idisDefaultProfile

KAUserProfile

profile_idrole_idisOptionnal

KAProfileRole*

*

*

*

idaccessList_idcode

KAAccessListItem

*

*

idpasswordHash

KAUser

Page 10: KAAccessControl

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.

Page 11: KAAccessControl

New app demo

Page 12: KAAccessControl

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

Page 13: KAAccessControl

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!# Migrationser.migration.migrateAtStartup=trueer.migration.createTablesIfNecessary=trueer.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.

Page 14: KAAccessControl

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 userRolesFileLoader.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");!

Page 15: KAAccessControl

Create the role.plist file (use the sample as template)

{lists = ( "Territory", "Wharehouse");roleGroups = ( { code = "Sales"; roles = ( { code = "CustomerSalesReport"; listCode = "Territory"; allowsMultipleItems = YES; }, { "code" = "SeeGrossProfits"; }, ); });profiles = ( { code = "Salesman"; roles = ( "CustomerSalesReport" ); optionalRoles = ( "SeeGrossProfits" ); });

Page 16: KAAccessControl

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

Page 17: KAAccessControl

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.

Page 18: KAAccessControl

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

Page 19: KAAccessControl

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().

Page 20: KAAccessControl

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

Page 21: KAAccessControl

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.

Page 22: KAAccessControl

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

Page 23: KAAccessControl

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

Page 24: KAAccessControl

ComponentAccessService

• This object read the component classes annotation.

• It is used in the checkAccess() method of your ERXComponents.

Page 25: KAAccessControl

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

Page 26: KAAccessControl

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

Page 27: KAAccessControl

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.

Page 28: KAAccessControl

Next features ?

• New password hashers.

• Support for LDAP authentication.

• UI to edit the roles.plist file.

• Adjustments for WOInject.

Page 29: KAAccessControl

Q&ASamuel Pelletier Kaviju inc. [email protected] https://github.com/spelletier