# SecurityConcern.java

    /*
     * Copyright (c) 2010, Paul Merlin. All Rights Reserved.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     */
    package org.qi4j.library.shiro.concerns;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.LinkedHashSet;
    import java.util.Set;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authz.UnauthenticatedException;
    import org.apache.shiro.authz.UnauthorizedException;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.PermissionUtils;
    import org.qi4j.api.common.AppliesTo;
    import org.qi4j.api.common.Optional;
    import org.qi4j.api.concern.ConcernOf;
    import org.qi4j.api.injection.scope.Invocation;
    import org.qi4j.library.shiro.Shiro;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    @AppliesTo( { RequiresAuthentication.class,
                  RequiresGuest.class,
                  RequiresPermissions.class,
                  RequiresRoles.class,
                  RequiresUser.class } )
    public class SecurityConcern
            extends ConcernOf<InvocationHandler>
            implements InvocationHandler
    {

        private static final Logger LOGGER = LoggerFactory.getLogger( Shiro.LOGGER_NAME );

        @Optional
        @Invocation
        private RequiresAuthentication requiresAuthentication;

        @Optional
        @Invocation
        private RequiresGuest requiresGuest;

        @Optional
        @Invocation
        private RequiresPermissions requiresPermissions;

        @Optional
        @Invocation
        private RequiresRoles requiresRoles;

        @Optional
        @Invocation
        private RequiresUser requiresUser;

        @Override
        public Object invoke( Object proxy, Method method, Object[] args )
                throws Throwable
        {
            Subject subject = SecurityUtils.getSubject();

            handleRequiresGuest( subject );
            handleRequiresUser( subject );
            handleRequiresAuthentication( subject );
            handleRequiresRoles( subject );
            handleRequiresPermissions( subject );

            return next.invoke( proxy, method, args );
        }

        private void handleRequiresGuest( Subject subject )
        {
            if ( requiresGuest != null ) {
                LOGGER.debug( "SecurityConcern::RequiresGuest" );
                if ( subject.getPrincipal() != null ) {
                    throw new UnauthenticatedException(
                            "Attempting to perform a guest-only operation. The current Subject is "
                            + "not a guest (they have been authenticated or remembered from a previous login).  Access "
                            + "denied." );

                }
            } else {
                LOGGER.debug( "SecurityConcern::RequiresGuest: not concerned" );
            }
        }

        private void handleRequiresUser( Subject subject )
        {
            if ( requiresUser != null ) {
                LOGGER.debug( "SecurityConcern::RequiresUser" );
                if ( subject.getPrincipal() == null ) {
                    throw new UnauthenticatedException(
                            "Attempting to perform a user-only operation. The current Subject is "
                            + "not a user (they haven't been authenticated or remembered from a previous login).  "
                            + "Access denied." );
                }
            } else {
                LOGGER.debug( "SecurityConcern::RequiresUser: not concerned" );
            }
        }

        private void handleRequiresAuthentication( Subject subject )
        {
            if ( requiresAuthentication != null ) {
                LOGGER.debug( "SecurityConcern::RequiresAuthentication" );
                if ( !subject.isAuthenticated() ) {
                    throw new UnauthenticatedException( "The current Subject is not authenticated.  Access denied." );
                }
            } else {
                LOGGER.debug( "SecurityConcern::RequiresAuthentication: not concerned" );
            }
        }

        private void handleRequiresRoles( Subject subject )
        {
            if ( requiresRoles != null ) {
                LOGGER.debug( "SecurityConcern::RequiresRoles" );
                String roleId = requiresRoles.value();
                String[] roles = roleId.split( "," );
                if ( roles.length == 1 ) {
                    if ( !subject.hasRole( roles[ 0] ) ) {
                        String msg = "Calling Subject does not have required role [" + roleId + "].  "
                                     + "MethodInvocation denied.";
                        throw new UnauthorizedException( msg );
                    }
                } else {
                    Set<String> rolesSet = new LinkedHashSet<String>( Arrays.asList( roles ) );
                    if ( !subject.hasAllRoles( rolesSet ) ) {
                        String msg = "Calling Subject does not have required roles [" + roleId + "].  "
                                     + "MethodInvocation denied.";
                        throw new UnauthorizedException( msg );
                    }
                }
            } else {
                LOGGER.debug( "SecurityConcern::RequiresRoles: not concerned" );
            }

        }

        private void handleRequiresPermissions( Subject subject )
        {
            if ( requiresPermissions != null ) {
                LOGGER.debug( "SecurityConcern::RequiresPermissions" );
                String permsString = requiresPermissions.value();
                Set<String> permissions = PermissionUtils.toPermissionStrings( permsString );
                if ( permissions.size() == 1 ) {
                    if ( !subject.isPermitted( permissions.iterator().next() ) ) {
                        String msg = "Calling Subject does not have required permission [" + permsString + "].  "
                                     + "Method invocation denied.";
                        throw new UnauthorizedException( msg );
                    }
                } else {
                    String[] permStrings = new String[ permissions.size() ];
                    permStrings = permissions.toArray( permStrings );
                    if ( !subject.isPermittedAll( permStrings ) ) {
                        String msg = "Calling Subject does not have required permissions [" + permsString + "].  "
                                     + "Method invocation denied.";
                        throw new UnauthorizedException( msg );
                    }

                }
            } else {
                LOGGER.debug( "SecurityConcern::RequiresPermissions: not concerned" );
            }

        }

    }