/*
 *
 *  * Copyright 2013 Jive Software
 *  *
 *  *    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 com.jivesoftware.sdk.service.filter;

import com.google.common.collect.Maps;
import com.jivesoftware.sdk.api.entity.JiveInstance;
import com.jivesoftware.sdk.api.entity.JiveInstanceProvider;
import com.jivesoftware.sdk.utils.JiveSDKUtils;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Created by rrutan on 1/30/14.
 */
@Singleton
public class JiveAuthorizationValidator {
    private static final Logger log = LoggerFactory.getLogger(JiveAuthorizationValidator.class);

    public static final String JIVE_INSTANCE = JiveAuthorizationValidation.class.getSimpleName()+".JiveInstance";

    private static final String PARAM_ALGORITHM = "algorithm";
    private static final String PARAM_CLIENT_ID = "client_id";
    private static final String PARAM_JIVE_URL = "jive_url";
    private static final String PARAM_TENANT_ID = "tenant_id";
    private static final String PARAM_TIMESTAMP = "timestamp";
    private static final String PARAM_SIGNATURE = "signature";

    private static final String JIVE_EXTN = "JiveEXTN ";

    private static final String QUERY_PARAM_SIGNATURE = "&" + PARAM_SIGNATURE + "=";

    private static final WebApplicationException BAD_REQUEST =
       new WebApplicationException(
           Response.status(Response.Status.BAD_REQUEST).build());

    private static final WebApplicationException UNAUTHORIZED =
       new WebApplicationException(
           Response.status(Response.Status.UNAUTHORIZED).build());

    @Inject
    private JiveInstanceProvider jiveInstanceProvider;

    public void authenticate(ContainerRequestContext request) {
        String authorization = request.getHeaderString(HttpHeaders.AUTHORIZATION);

        if (log.isTraceEnabled()) { log.trace("Authz Header:\n"+authorization); }

        if (!authorization.startsWith(JIVE_EXTN) || !authorization.contains(QUERY_PARAM_SIGNATURE)) {
            if (log.isInfoEnabled()) { log.info("Jive authorization isn't properly formatted: " + authorization); }
            throw BAD_REQUEST;
        } // end if

        Map<String, String> paramMap = getParamsFromAuthz(authorization);

        if (log.isDebugEnabled()) { log.debug("Authz Parameters: \n"+paramMap); }

        String signature = paramMap.get(PARAM_SIGNATURE);
        String algorithm = paramMap.get(PARAM_ALGORITHM);
        String clientId = paramMap.get(PARAM_CLIENT_ID);
        String jiveUrl = paramMap.get(PARAM_JIVE_URL);
        String tenantId = paramMap.get(PARAM_TENANT_ID);
        String timeStampStr = paramMap.get(PARAM_TIMESTAMP);

        if (!JiveSDKUtils.isAllExist(algorithm, clientId, jiveUrl, tenantId, timeStampStr)) {
            log.error("Jive authorization is partial: " + paramMap);
            throw BAD_REQUEST;
        } // end if

        long timeStamp = Long.parseLong(timeStampStr);
        long millisPassed = System.currentTimeMillis() - timeStamp;
        if (millisPassed < 0 || millisPassed > TimeUnit.MINUTES.toMillis(5)) {
            log.error("Jive authorization is rejected since it's " + millisPassed + "ms old (max. allowed is 5 minutes): " + paramMap);
            throw UNAUTHORIZED;
        } // end if

        JiveInstance jiveInstance = jiveInstanceProvider.getInstanceByTenantId(tenantId);

        if (jiveInstance == null) {
            log.error("Jive authorization failed due to invalid tenant ID: " + tenantId);
            throw UNAUTHORIZED;
        } // end if

        String expectedClientId = jiveInstance.getClientId();
        if (!clientId.equals(expectedClientId)) {
            String msg = String.format("Jive authorization failed due to missing Client ID: Actual [%s], Expected [%s]", clientId, expectedClientId);
            log.error(msg);
            throw UNAUTHORIZED;
        } // end if

        String clientSecret = jiveInstance.getClientSecret();
        String paramStrWithoutSignature = authorization.substring(JIVE_EXTN.length(), authorization.indexOf(QUERY_PARAM_SIGNATURE));

        try {
            String expectedSignature = sign(paramStrWithoutSignature, clientSecret, algorithm);
            if (expectedSignature.equals(signature)) {
                //SAVING jiveInstance INSTANCE TO REQUEST
                //TODO:
                request.setProperty(JIVE_INSTANCE,jiveInstance);
            } else {
                log.error("Jive authorization failed due to tampered signature! Original authz: " + authorization);
                throw UNAUTHORIZED;
            } // end if
        } catch (Exception e) {
            log.error("Failed validating Jive auth. scheme"+e.getMessage());
            throw UNAUTHORIZED;
        } // end try/catch

    } // end authenticate

    @Nonnull
     private String sign(@Nonnull String str, @Nonnull String clientSecret, @Nonnull String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
         byte[] secret = Base64.decodeBase64(clientSecret);
         SecretKeySpec secretKeySpec = new SecretKeySpec(secret, algorithm);
         Mac mac = Mac.getInstance(algorithm);
         mac.init(secretKeySpec);
         mac.update(str.getBytes("UTF-8"));
         return Base64.encodeBase64String(mac.doFinal()).replaceAll("\\s+", "");
     } // end sign

    @Nonnull
    private Map<String, String> getParamsFromAuthz(String authHeader) {
        if (!authHeader.startsWith(JIVE_EXTN)) {
            return Maps.newHashMap();
        } // end if

        authHeader = authHeader.substring(JIVE_EXTN.length());
        String[] params = authHeader.split("[?|&]");
        Map<String, String> paramMap = Maps.newHashMap();
        for (String param : params) {
            String[] tokens = param.split("=");
            if (tokens.length != 2) {
                return Maps.newHashMap();
            } // end if

            paramMap.put(JiveSDKUtils.decodeUrl(tokens[0]), JiveSDKUtils.decodeUrl(tokens[1]));
        } // end for param

        return paramMap;
    } // end getParamsFromAuthz

} // end class

@@ -747,16 +747,66 @@
 t.Maps;%0A
+import com.jivesoftware.sdk.JiveAddOnApplication;%0A
 import c
@@ -2751,16 +2751,81 @@
 );%0A%0A    
+// ambiguous reference when multiple implementations exist%0A    //
 @Inject%0A
@@ -2824,24 +2824,26 @@
 @Inject%0A    
+//
 private Jive
@@ -2882,16 +2882,84 @@
 vider;%0A%0A
+    @Inject%0A    private JiveAddOnApplication jiveAddOnApplication;%0A%0A
     publ
@@ -4570,16 +4570,18 @@
         
+//
 JiveInst
@@ -4653,16 +4653,132 @@
 nantId);
+%0A        JiveInstance jiveInstance = jiveAddOnApplication.getJiveInstanceProvider().getInstanceByTenantId(tenantId);
 %0A%0A      

fix jive instance provider dependency