/*
 * 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.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.dao.OAuthTokenPersistenceFactory;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;

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

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 cookie based token binder implementation.
 */
public class CookieBasedTokenBinder extends AbstractTokenBinder {

    private static final String BINDING_TYPE = "cookie";

    private static final String COOKIE_NAME = "atbv";

    private List<String> supportedGrantTypes = Collections.singletonList(AUTHORIZATION_CODE);

    @Override
    public String getBindingType() {

        return BINDING_TYPE;
    }

    @Override
    public List<String> getSupportedGrantTypes() {

        return Collections.unmodifiableList(supportedGrantTypes);
    }

    @Override
    public String getDisplayName() {

        return "Cookie Based";
    }

    @Override
    public String getDescription() {

        return "Bind token to the browser cookie. Supported grant types: Code";
    }

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

        String tokenBindingValue = retrieveTokenBindingValueFromRequest(request);

        if (StringUtils.isNotBlank(tokenBindingValue)) {
            return tokenBindingValue;
        } else {
            return UUID.randomUUID().toString();
        }
    }

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

        String tokenBindingValue = retrieveTokenBindingValueFromRequest(request);

        if (StringUtils.isNotBlank(tokenBindingValue)) {
            return tokenBindingValue;
        } else {
            throw new OAuthSystemException("Failed to retrieve token binding value.");
        }
    }

    private String retrieveTokenBindingValueFromRequest(HttpServletRequest request) throws OAuthSystemException {

        Cookie[] cookies = request.getCookies();
        if (ArrayUtils.isEmpty(cookies)) {
            return null;
        }

        Optional<Cookie> tokenBindingCookieOptional = Arrays.stream(cookies)
                .filter(t -> COOKIE_NAME.equals(t.getName())).findAny();
        if (!tokenBindingCookieOptional.isPresent() || StringUtils
                .isBlank(tokenBindingCookieOptional.get().getValue())) {
            return null;
        }

        String tokenBindingValue = tokenBindingCookieOptional.get().getValue();
        boolean isTokenBindingValueValid;
        try {
            // Do we need additional validation here? like validate local user.
            isTokenBindingValueValid = OAuthTokenPersistenceFactory.getInstance().getTokenBindingMgtDAO()
                    .isTokenBindingExistsForBindingReference(OAuth2Util.getTokenBindingReference(tokenBindingValue));
        } catch (IdentityOAuth2Exception e) {
            throw new OAuthSystemException("Failed to check token binding reference existence", e);
        }

        return isTokenBindingValueValid ? tokenBindingValue : null;
    }

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

        Cookie cookie = new Cookie(COOKIE_NAME, bindingValue);
        cookie.setSecure(true);
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        response.addCookie(cookie);
    }

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

        Cookie[] cookies = request.getCookies();
        if (ArrayUtils.isNotEmpty(cookies)) {
            Arrays.stream(cookies).filter(t -> COOKIE_NAME.equals(t.getName())).findAny().ifPresent(cookie -> {
                cookie.setMaxAge(0);
                cookie.setSecure(true);
                cookie.setHttpOnly(true);
                cookie.setPath("/");
                response.addCookie(cookie);
            });
        }
    }

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

        return isValidTokenBinding(request, bindingReference, COOKIE_NAME);
    }

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

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

@@ -874,32 +874,119 @@
 ystemException;%0A
+import org.wso2.carbon.core.SameSiteCookie;%0Aimport org.wso2.carbon.core.ServletCookie;%0A
 import org.wso2.
@@ -4550,24 +4550,31 @@
  %7B%0A%0A        
+Servlet
 Cookie cooki
@@ -4581,16 +4581,23 @@
 e = new 
+Servlet
 Cookie(C
@@ -4710,32 +4710,81 @@
 e.setPath(%22/%22);%0A
+        cookie.setSameSite(SameSiteCookie.NONE);%0A
         response
@@ -5142,33 +5142,142 @@
                 
-c
+ServletCookie servletCookie = new ServletCookie(cookie.getName(), cookie.getValue());%0A                servletC
 ookie.setMaxAge(
@@ -5288,33 +5288,40 @@
                 
-c
+servletC
 ookie.setSecure(
@@ -5335,33 +5335,40 @@
                 
-c
+servletC
 ookie.setHttpOnl
@@ -5384,33 +5384,40 @@
                 
-c
+servletC
 ookie.setPath(%22/
@@ -5412,32 +5412,96 @@
 e.setPath(%22/%22);%0A
+                servletCookie.setSameSite(SameSiteCookie.NONE);%0A
                 
@@ -5511,33 +5511,40 @@
 ponse.addCookie(
-c
+servletC
 ookie);%0A        

Add sameSite attribute for atbv cookie