Authorization is the function of specifying access rights to resources, which is related to information security and computer security in general and to access control in particular. More formally, to authorize is to
define access policy.
Authorization is the function of specifying access rights to resources, which is related to information security and computer security in general and to access control in particular. More formally, to authorize is to
define access policy.
HOW TO DEFINE POLICY THE EASIEST and RELIABLE WAY
public void updateOrganization(Organization organization) { GastroUser principal = SecurityContext.getInstance().getPrincipal(); if (principal != null && (principal.isAdministrator() || principal.isOrganizationOwner(organization.getId()))) { //check preconditions Assert.isTrue( organization.isApproved(), "Cannot update organization that hasn't been yet approved!" ); //do update logic updateOrganizationBySystem(organization); //inform the system about the action publisher.publishEvent(new OrganizationUpdatedEvent(this, organization)); else { throw new AccessDeniedException("User is not authorized to execute this action!"); } }
Six out of 16 lines to each method to execute authorization?
/** This is shared **/ public static void authorizeAdministrator(Organization organization) { GastroUser principal = SecurityContext.getInstance().getPrincipal(); if (!(principal != null && (principal.isAdministrator() || principal.isOrganizationOwner(organization.getId())))) { throw new AccessDeniedException("User is not authorized to execute this action!"); } } public void updateOrganization(Organization organization) { authorizeAdministrator(organization); Assert.isTrue( organization.isApproved(), "Cannot update organization that was not yet approved!" ); updateOrganizationBySystem(organization); publisher.publishEvent(new OrganizationUpdatedEvent(this, organization)); }
Well now we have single line but we are forced to use only static methods and singletons to keep it pretty.
@Pointcut("execution(* com.fg.whatever.business..*.update*(..))") public static void authorizeAdministrator(Organization organization!!!) { GastroUser principal = SecurityContext.getInstance().getPrincipal(); if (!(principal != null && (principal.isAdministrator() || principal.isOrganizationOwner(organization.getId())))) { throw new AccessDeniedException("User is not authorized to execute this action!"); } } public void updateOrganization(Organization organization) { Assert.isTrue( organization.isApproved(), "Cannot update organization that was not yet approved!" ); updateOrganizationBySystem(organization); publisher.publishEvent(new OrganizationUpdatedEvent(this, organization)); }
We have to think about unified naming schemes and with complex authorization this solution quickly runs of breath.
@PreAuthorize( "principal.userObject != null and " + "(principal.userObject.administrator or principal.userObject.isOwnerOf(#organization.id))" ) public void updateOrganization(Organization organization) { Assert.isTrue( organization.isApproved(), "Cannot update organization that was not yet approved!" ); updateOrganizationBySystem(organization); publisher.publishEvent(new OrganizationUpdatedEvent(this, organization)); }
Half way there - even if this is currently recommended and standard solution.
private static final String IS_ADMINISTRATOR = "..."; private static final String IS_ORGANIZATION_OWNER = "..."; @PreAuthorize(IS_ADMINISTRATOR + " or " + IS_ORGANIZATION_OWNER) public void updateOrganization(Organization organization) { Assert.isTrue( organization.isApproved(), "Cannot update organization that was not yet approved!" ); updateOrganizationBySystem(organization); publisher.publishEvent(new OrganizationUpdatedEvent(this, organization)); }
Better - but we cannot look up for all methods that are protected to admin access.
@AllowedForAdministrator @AllowedForEdeeAdmin @AllowedForOrganizationOwner @DeniedForMerchant public void updateOrganization(Organization organization) { Assert.isTrue( organization.isApproved(), "Cannot update organization that was not yet approved!" ); updateOrganizationBySystem(organization); publisher.publishEvent(new OrganizationUpdatedEvent(this, organization)); }
This is something I use now in my projects.
@Test(expected = AccessDeniedException.class) public void anonymousUserShouldBeDeniedToUpdateOrganization() throws Exception { Organization company = organizationManager.getOrganizationById(2); organizationManager.approveOrganization(company); } @Test @RunAsUser("soltysova@fg.cz") public void administratorShouldBeAbleToUpdateOrganization() throws Exception { final Organization company = organizationManager.getOrganizationById(2); organizationManager.updateOrganization(company); }
I test the logic alongside with authorization aspect - but I can separate it if necessary.
@Test @RunAsUser("cizek@fg.cz") public void administratorCanCancelBonusActionOfTheUser() throws Exception { //in the anonymous class we are logged as soltysova@fg - user RunAsSupportTestExecutionListener.executeInContextOf( "soltysova@fg.cz", applicationContext, new RunAsSupportTestExecutionListener.ExecutionClosure() { @Override public void execute() { Bonus inPreparationBonus = bonusManager.getBonusById(20); bonusManager.publishBonus(inPreparationBonus, Locale.ENGLISH); } } ); //in this context we are cizek@fg.cz - administrator Bonus bonusToCancel = bonusManager.getBonusById(20); bonusManager.cancelBonus(bonusToCancel, "I am cancelling the action.", true); assertEquals(ProductState.CANCELED, bonusManager.getBonusById(20).getState()); }
Inside single test I could switch user contexts as I wish. Closures would help here though.
Thank you for sharing!