//--- BEGIN COPYRIGHT BLOCK ---
//This program is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; version 2 of the License.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License along
//with this program; if not, write to the Free Software Foundation, Inc.,
//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
//(C) 2012 Red Hat, Inc.
//All rights reserved.
//--- END COPYRIGHT BLOCK ---
package com.netscape.certsrv.acls;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.Principal;
import java.util.Properties;

import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.Precedence;
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.authentication.IAuthToken;
import com.netscape.certsrv.authorization.AuthzToken;
import com.netscape.certsrv.authorization.EAuthzAccessDenied;
import com.netscape.certsrv.authorization.IAuthzSubsystem;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.ForbiddenException;
import com.netscape.cmscore.realm.PKIPrincipal;


/**
 * @author Endi S. Dewata
 */
@Provider
@ServerInterceptor
@Precedence("SECURITY")
public class ACLInterceptor implements PreProcessInterceptor {

    Properties authProperties;

    @Context
    ServletContext servletContext;

    @Context
    SecurityContext securityContext;

    public synchronized void loadAuthProperties() throws IOException {

        if (authProperties != null) return;

        URL url = servletContext.getResource("/WEB-INF/auth.properties");
        authProperties = new Properties();
        authProperties.load(url.openStream());
    }

    @Override
    public ServerResponse preProcess(
            HttpRequest request,
            ResourceMethod resourceMethod
        ) throws Failure, ForbiddenException {

        // Get ACL mapping for the method.
        Method method = resourceMethod.getMethod();
        ACLMapping aclMapping = method.getAnnotation(ACLMapping.class);

        // If not available, get ACL mapping for the class.
        if (aclMapping == null) {
            Class<?> clazz = method.getDeclaringClass();
            aclMapping = clazz.getAnnotation(ACLMapping.class);
        }

        // If still not available, it's unprotected, allow request.
        if (aclMapping == null) return null;

        Principal principal = securityContext.getUserPrincipal();

        // If unauthenticated, reject request.
        if (principal == null) {
            throw new ForbiddenException("No user principal provided.");
        }

        // If unrecognized principal, reject request.
        if (!(principal instanceof PKIPrincipal)) {
            throw new ForbiddenException("Invalid user principal");
        }

        PKIPrincipal pkiPrincipal = (PKIPrincipal)principal;
        IAuthToken authToken = pkiPrincipal.getAuthToken();

        // If missing auth token, reject request.
        if (authToken == null) {
            throw new ForbiddenException("No authorization token present.");
        }

        try {
            loadAuthProperties();

            String name = aclMapping.value();
            String value = authProperties.getProperty(name);

            // If no property defined, allow request.
            if (value == null) return null;

            String values[] = value.split(",");

            // If invalid mapping, reject request.
            if (values.length != 2) {
                throw new ForbiddenException("Invalid ACL mapping.");
            }

            // Check authorization.
            IAuthzSubsystem mAuthz = (IAuthzSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTHZ);
            AuthzToken authzToken = mAuthz.authorize(
                    "DirAclAuthz",
                    authToken,
                    values[0], // resource
                    values[1]); // operation

            // If not authorized, reject request.
            if (authzToken == null) {
                throw new ForbiddenException("No authorization token present.");
            }

        } catch (EAuthzAccessDenied e) {
            throw new ForbiddenException(e.toString());

        } catch (IOException|EBaseException e) {
            e.printStackTrace();
            throw new Failure(e);
        }

        // Allow request.
        return null;
    }
}
