What is authorization

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.

The question is ...

Description

HOW TO DEFINE POLICY THE EASIEST and RELIABLE WAY

The imperative 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?

Let's refactor this ...

            /** 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.

The declarative way - AOP

Isn't it a sample example of crosscutting concern?

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

How does Spring solve it?

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

Let's refactor expression to share the logic ...

            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.

Let's refactor expression to share the logic ...

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

How do I test authorization logic?

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

Sometimes we need to change user contexts

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

... and how do YOU fight with authorization?

Thank you for sharing!