/*
 * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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.wso2.carbon.identity.oauth2.token.bindings.impl;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.wso2.carbon.identity.application.authentication.framework.context.SessionContext;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.oauth2.OAuth2Constants;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.AUTHORIZATION_CODE;

/**
 * This class provides the sso session based token binder implementation. This will generate new access token for
 * each new session.
 */
public class SSOSessionBasedTokenBinder extends AbstractTokenBinder {

    private List<String> supportedGrantTypes = Collections.singletonList(AUTHORIZATION_CODE);
    private static final String COMMONAUTH_COOKIE = "commonAuthId";
    private static final Log log = LogFactory.getLog(SSOSessionBasedTokenBinder.class);

    @Override
    public String getDisplayName() {

        return "SSO Session Based";
    }

    @Override
    public String getDescription() {

        return "Bind token to the SSO session. Supported grant types: Code";
    }

    @Override
    public String getBindingType() {

        return OAuth2Constants.TokenBinderType.SSO_SESSION_BASED_TOKEN_BINDER;
    }

    @Override
    public List<String> getSupportedGrantTypes() {

        return Collections.unmodifiableList(supportedGrantTypes);
    }

    @Override
    public String getOrGenerateTokenBindingValue(HttpServletRequest request) throws OAuthSystemException {

        return retrieveTokenBindingValueFromRequest(request);
    }

    @Override
    public String getTokenBindingValue(HttpServletRequest request) throws OAuthSystemException {

        return retrieveTokenBindingValueFromRequest(request);
    }

    private String retrieveTokenBindingValueFromRequest(HttpServletRequest request) throws OAuthSystemException {

        Cookie[] cookies = request.getCookies();
        String commonAuthCookieValueFromRequestAttribute = (String) request.getAttribute(COMMONAUTH_COOKIE);
        if (ArrayUtils.isEmpty(cookies)) {
            if (StringUtils.isNotEmpty(commonAuthCookieValueFromRequestAttribute)) {
                return DigestUtils.sha256Hex(commonAuthCookieValueFromRequestAttribute);
            }
            throw new OAuthSystemException("Failed to retrieve token binding value.");
        }

        Optional<Cookie> commonAuthCookieOptional = Arrays.stream(cookies)
                .filter(t -> COMMONAUTH_COOKIE.equals(t.getName())).findAny();

        if ((!commonAuthCookieOptional.isPresent() || StringUtils.isBlank(commonAuthCookieOptional.get().getValue()))
                && StringUtils.isEmpty(commonAuthCookieValueFromRequestAttribute)) {
            throw new OAuthSystemException("Failed to retrieve token binding value.");
        }

        // Get the session context key value form common auth cookie value.
        return commonAuthCookieOptional
                .map(cookie -> DigestUtils.sha256Hex(cookie.getValue()))
                .orElseGet(() -> DigestUtils.sha256Hex(commonAuthCookieValueFromRequestAttribute));
    }

    @Override
    public void setTokenBindingValueForResponse(HttpServletResponse response, String bindingValue) {

        // Not required.
    }

    @Override
    public void clearTokenBindingElements(HttpServletRequest request, HttpServletResponse response) {

        // Not required.
    }

    @Override
    public boolean isValidTokenBinding(Object request, String bindingReference) {

        try {
            String sessionIdentifier = getTokenBindingValue((HttpServletRequest) request);
            if (StringUtils.isBlank(sessionIdentifier)) {
                if (log.isDebugEnabled()) {
                    log.debug("CommonAuthId cookie is not found in the request.");
                }
                return false;
            }
            /* Retrieve session context information using sessionIdentifier in order to check the validity of
            commonAuthId cookie.*/
            SessionContext sessionContext = FrameworkUtils.getSessionContextFromCache(sessionIdentifier);
            if (sessionContext == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Session context is not found corresponding to the session identifier: " +
                            sessionIdentifier);
                }
                return false;
            }
        } catch (OAuthSystemException e) {
            log.error("Error while getting the token binding value", e);
            return false;
        }
        return isValidTokenBinding(request, bindingReference, COMMONAUTH_COOKIE);
    }

    @Override
    public boolean isValidTokenBinding(OAuth2AccessTokenReqDTO oAuth2AccessTokenReqDTO, String bindingReference) {

        return isValidTokenBinding(oAuth2AccessTokenReqDTO, bindingReference, COMMONAUTH_COOKIE);
    }
}

@@ -3295,32 +3295,35 @@
 f (ArrayUtils.is
+Not
 Empty(cookies)) 
@@ -3332,290 +3332,8 @@
     
-        if (StringUtils.isNotEmpty(commonAuthCookieValueFromRequestAttribute)) %7B%0A                return DigestUtils.sha256Hex(commonAuthCookieValueFromRequestAttribute);%0A            %7D%0A            throw new OAuthSystemException(%22Failed to retrieve token binding value.%22);%0A        %7D%0A%0A
     
@@ -3419,16 +3419,20 @@
         
+    
 .filter(
@@ -3499,14 +3499,16 @@
     
+    
 if (
-(!
 comm
@@ -3544,10 +3544,30 @@
 t() 
-%7C%7C
+&&%0A                   
  Str
@@ -3573,24 +3573,27 @@
 ringUtils.is
+Not
 Blank(common
@@ -3621,32 +3621,34 @@
 t().getValue()))
+ %7B
 %0A               
@@ -3652,161 +3652,78 @@
     
-&& StringUtils.isEmpty(commonAuthCookieValueFromRequestAttribute)) %7B%0A            throw new OAuthSystemException(%22Failed to retrieve token binding v
+return DigestUtils.sha256Hex(commonAuthCookieOptional.get().getV
 alue
-.%22
+()
 );%0A 
@@ -3725,26 +3725,29 @@
 ));%0A        
+ 
+   
 %7D
-%0A
 %0A        // 
@@ -3747,286 +3747,266 @@
     
-// Get the session context key value form common auth cookie value.%0A        return commonAuthCookieOptional%0A                .map(cookie -%3E DigestUtils.sha256Hex(cookie.getValue()))%0A                .orElseGet(() -%3E DigestUtils.sha256Hex(commonAuthCookieValueFromRequestAttribute)
+%7D%0A        if (StringUtils.isNotEmpty(commonAuthCookieValueFromRequestAttribute)) %7B%0A            return DigestUtils.sha256Hex(commonAuthCookieValueFromRequestAttribute);%0A        %7D%0A        throw new OAuthSystemException(%22Failed to retrieve token binding value.%22
 );%0A 

Address comments