package net.unicon.cas.addons.serviceregistry.services.authorization;

import net.unicon.cas.addons.authentication.AuthenticationSupport;
import net.unicon.cas.addons.authentication.internal.DefaultAuthenticationSupport;
import net.unicon.cas.addons.serviceregistry.RegisteredServiceWithAttributes;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.ticket.registry.TicketRegistry;
import org.jasig.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

/**
 * An action state to be executed for the authorization check based on registered service attributes before vending a service ticket.
 * <p/>
 * It is expected that this action is to be inserted as the first action of the <code>generateServiceTicket</code> action state in the login web flow definition.
 *
 * @author Dmitriy Kopylenko
 * @author Unicon, inc.
 * @since 1.5
 */
public class ServiceAuthorizationAction extends AbstractAction {

    private final ServicesManager servicesManager;

    private final RegisteredServiceAuthorizer authorizer;

    private final AuthenticationSupport authenticationSupport;

    private static final String AUTHZ_ATTRS_KEY = "authzAttributes";

    private static final String AUTHZ_FAIL_REDIRECT_URL_KEY = "authorizationFailureRedirectUrl";

    private static final String ATTR_URL_KEY = "unauthorizedRedirectUrl";

    private static final Logger logger = LoggerFactory.getLogger(ServiceAuthorizationAction.class);


    public ServiceAuthorizationAction(ServicesManager servicesManager, TicketRegistry ticketRegistry, RegisteredServiceAuthorizer registeredServiceAuthorizer) {
        this.servicesManager = servicesManager;
        this.authorizer = registeredServiceAuthorizer;
        this.authenticationSupport = new DefaultAuthenticationSupport(ticketRegistry);
    }

    @Override
    protected Event doExecute(RequestContext requestContext) throws Exception {
        final Principal principal = this.authenticationSupport.getAuthenticatedPrincipalFrom(WebUtils.getTicketGrantingTicketId(requestContext));
        //Guard against expired SSO sessions. 'error' event should trigger the transition to the 'generateLoginTicket' state
        if (principal == null) {
            logger.warn("The SSO session is no longer valid. Restarting the login process...");
            return error();
        }
        final Object principalAttributes = principal.getAttributes();
        final String principalId = principal.getId();
        final Service service = WebUtils.getService(requestContext);
        final String serviceId = service.getId();

        //Now do the actual RBAC authorization comparing the principal's attributes and registered service's defined attributes
        RegisteredServiceWithAttributes registeredService = (RegisteredServiceWithAttributes) this.servicesManager.findServiceBy(service);
        Object serviceAttributes = registeredService.getExtraAttributes().get(AUTHZ_ATTRS_KEY);
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("SERVICE [%s] ATTRIBUTES: %s | PRINCIPAL [%s] ATTRIBUTES: %s",
                    registeredService.getServiceId(), serviceAttributes, principalId, principalAttributes));
        }
        if (!this.authorizer.authorized(serviceAttributes, principalAttributes)) {
            logger.info("Principal [{}] is not authorized to use service [{}]", principalId, serviceId);
            requestContext.getRequestScope().put(AUTHZ_FAIL_REDIRECT_URL_KEY, registeredService.getExtraAttributes().get(ATTR_URL_KEY));
            //Should be handled in the global transition handler to do the actual external redirect to a specific service's URL
            throw new RoleBasedServiceAuthorizationException();
        }
        logger.info("Principal [{}] is authorized to use service [{}]", principalId, serviceId);

        //Everything is fine. Continue with the main service ticket generation action state execution. null will signal to SWF to try execution of the next action in the chain
        // which should be 'GenerateServiceTicketAction'
        return null;
    }
}

@@ -547,16 +547,76 @@
 bUtils;%0A
+import org.jasig.cas.services.UnauthorizedServiceException;%0A
 import o
@@ -1803,17 +1803,16 @@
 lass);%0A%0A
-%0A
     publ
@@ -2951,72 +2951,80 @@
   //
-Now do the actual RBAC authorization comparing the principal's a
+Find this service in the service registry%0A        RegisteredServiceWithA
 ttri
@@ -3033,12 +3033,8 @@
 tes 
-and 
 regi
@@ -3043,37 +3043,97 @@
 ered
- s
+S
 ervice
-'s defined attributes
+ = (RegisteredServiceWithAttributes) this.servicesManager.findServiceBy(service); 
 %0A   
@@ -3129,33 +3129,37 @@
 vice); %0A        
-R
+if (r
 egisteredService
@@ -3162,218 +3162,817 @@
 vice
-WithAttributes registeredService = (RegisteredServiceWithAttributes) this.servicesManager.findServiceBy(service);%0A        Object serviceAttributes = registeredService.getExtraAttributes().get(AUTHZ_ATTRS_KEY);%0A
+ == null) %7B%0A          logger.warn(%22Unauthorized Service Access for Service: %5B %7B%7D %5D - service is not defined in the service registry.%22, serviceId);%0A          throw new UnauthorizedServiceException();%0A        %7D else if (!registeredService.isEnabled()) %7B%0A          logger.warn(%22Unauthorized Service Access for Service: %5B %7B%7D %5D - service is not enabled in the service registry.%22, serviceId);%0A          throw new UnauthorizedServiceException();%0A        %7D%0A%0A        //Check to see if RBAC rules have been added to this service's configuration%0A        Object serviceAttributes = registeredService.getExtraAttributes().get(AUTHZ_ATTRS_KEY);%0A        if(serviceAttributes == null)%7B%0A          logger.info(%22Service %5B%7B%7D%5D is not configured for role-based authorization%22, registeredService.getServiceId());%0A        %7D else %7B%0A  
     
@@ -3998,32 +3998,34 @@
 bugEnabled()) %7B%0A
+  
             logg
@@ -4130,16 +4130,18 @@
         
+  
 register
@@ -4221,34 +4221,168 @@
 utes));%0A        
+ 
+ 
 %7D%0A
+          //Now do the actual RBAC authorization comparing the principal's attributes and registered service's defined attributes%0A  
         if (!thi
@@ -4444,24 +4444,26 @@
 ributes)) %7B%0A
+  
             
@@ -4567,16 +4567,18 @@
         
+ 
+ 
 requestC
@@ -4706,16 +4706,18 @@
         
+  
 //Should
@@ -4828,32 +4828,34 @@
 URL%0A            
+ 
+ 
 throw new RoleBa
@@ -4890,34 +4890,38 @@
 tion();%0A        
-%7D%0A
+  %7D%0A  
         logger.i
@@ -5001,16 +5001,34 @@
 iceId);%0A
+        %7D%0A        
 %0A       

Allow services without RBAC rules to use CAS