package org.wso2.carbon.apimgt.rest.api.util.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.message.Message;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.api.model.*;
import org.wso2.carbon.apimgt.impl.factory.KeyManagerHolder;
import org.wso2.carbon.apimgt.rest.api.util.RestApiConstants;
import org.wso2.carbon.apimgt.rest.api.util.authenticators.WebAppAuthenticator;
import org.wso2.carbon.apimgt.rest.api.util.utils.RestApiUtil;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import org.wso2.uri.template.URITemplateException;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This web app authenticator class specifically implemented for API Manager store and publisher rest APIs
 * This will not be able to use as generic authenticator.
 */
public class WebAppAuthenticatorImpl implements WebAppAuthenticator {

    private static final Log log = LogFactory.getLog(WebAppAuthenticatorImpl.class);
    private static final String REGEX_BEARER_PATTERN = "Bearer\\s";
    private static final Pattern PATTERN = Pattern.compile(REGEX_BEARER_PATTERN);



    public boolean authenticate(Message message) throws APIManagementException {
        String accessToken = RestApiUtil.extractOAuthAccessTokenFromMessage(message,PATTERN,
                RestApiConstants.AUTH_HEADER_NAME);
        AccessTokenInfo tokenInfo = null;
        try {
            tokenInfo = KeyManagerHolder.getKeyManagerInstance().getTokenMetaData(accessToken);
        } catch (APIManagementException e) {
            log.error("Error while retrieving token information for token: " + accessToken + e.getMessage());
        }
        // if we got valid access token we will proceed with next
        if (tokenInfo != null && tokenInfo.isTokenValid()) {
            // If token is valid then we have to do other validations and set user and tenant to
            //carbon context. Scope validation should come here.
            //If access token is valid then we will perform scope check for given resource.
            if (validateScopes(message, tokenInfo)) {
                //If scope validation successful then set tenant name and user name to current context
                String tenantDomain = MultitenantUtils.getTenantDomain(tokenInfo.getEndUserName());
                int tenantId;
                PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
                RealmService realmService = (RealmService) carbonContext.getOSGiService(RealmService.class, null);
                try {
                    tenantId = realmService.getTenantManager().getTenantId(tenantDomain);
                    carbonContext.setTenantDomain(tenantDomain);
                    carbonContext.setTenantId(tenantId);
                    carbonContext.setUsername(tokenInfo.getEndUserName());
                    return true;
                } catch (UserStoreException e) {
                    log.error("Error while retrieving tenant id for tenant domain: " + tenantDomain);
                }
            } else {
                log.error("You cannot access API as scope validation failed");
            }
        } else {
            log.error(String.format("Authentication failed. Please check your username/password"));
        }
        return false;
    }




    /**
     *
     * @param message CXF message to be validate
     * @param tokenInfo Token information associated with incoming request
     * @return return true if we found matching scope in resource and token information
     *         else false(means scope validation failed).
     */
    private boolean validateScopes(Message message, AccessTokenInfo tokenInfo) {
        boolean authorized = false;
        String basePath = (String) message.get(Message.BASE_PATH);
        String path = (String) message.get(Message.PATH_INFO);
        String httpVerb = (String) message.get("org.apache.cxf.request.method");
        String resource = path.substring(basePath.length() - 1);
        String[] scopes = tokenInfo.getScopes();
        Set<URITemplate> uriTemplates = new HashSet<URITemplate>();
        if (basePath.contains("/api/am/publisher/")) {
            //this is publisher API so pick that API
            uriTemplates = RestApiUtil.getPublisherAppResourceMapping();
        } else if (basePath.contains("/api/am/store/")) {
            uriTemplates = RestApiUtil.getStoreAppResourceMapping();
        } else {
            log.error("No matching scope validation logic found for app request with path: " + basePath);
        }
        for (Object template : uriTemplates.toArray()) {
            org.wso2.uri.template.URITemplate templateToValidate = null;
            Map<String, String> var = new HashMap<String, String>();
            //check scopes with what we have
            String templateString = ((URITemplate) template).getUriTemplate().toString();
            try {
                templateToValidate = new org.wso2.uri.template.URITemplate(templateString);
            } catch (URITemplateException e) {
                log.error("Error while creating URI Template object to validate request. Template pattern: " + templateString);
            }
            if (templateToValidate.matches(resource, var)) {
                for (int i = 0; i < scopes.length; i++) {
                    Scope scp = ((URITemplate) template).getScope();
                    if (scp != null) {
                        if (scopes[i].equalsIgnoreCase(scp.getKey())) {
                            //we found scopes matches
                            if (log.isDebugEnabled()) {
                                log.debug("Scope validation successful for access token: " +
                                        tokenInfo.getAccessToken().toString() + " with scope: " + scp.getKey() +
                                        " for resource path: " + path);
                            }
                            return true;
                        }
                    }
                }
            }
        }
        return authorized;
    }
}


@@ -1377,16 +1377,224 @@
 ERN);%0A%0A%0A
+    /**%0A     * @param message cxf message to be authenticated%0A     * @return true if authentication was successful else false%0A     * @throws APIManagementException when error in authentication process%0A     */
 %0A    pub
@@ -1751,16 +1751,17 @@
 message,
+ 
 PATTERN,
@@ -3806,26 +3806,24 @@
     %7D%0A%0A%0A
-%0A%0A
     /**%0A
      *%0A 
@@ -3818,15 +3818,8 @@
 /**%0A
-     *%0A
     
@@ -3836,16 +3836,18 @@
 message 
+  
 CXF mess
@@ -5646,16 +5646,40 @@
 ern: %22 +
+%0A                       
  templat

refactor utility package