Merge pull request #8611 from YevhenBondarenko/cookie-util-improvements
ser/des in json format
This commit is contained in:
		
						commit
						1183195d23
					
				@ -15,22 +15,30 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.security.auth.oauth2;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.JsonProcessingException;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.util.SerializationUtils;
 | 
			
		||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.Cookie;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.ObjectInputStream;
 | 
			
		||||
import java.io.ObjectStreamClass;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Base64;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class CookieUtils {
 | 
			
		||||
 | 
			
		||||
    private static final ObjectMapper OBJECT_MAPPER;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        ClassLoader loader = CookieUtils.class.getClassLoader();
 | 
			
		||||
        OBJECT_MAPPER = new ObjectMapper();
 | 
			
		||||
        OBJECT_MAPPER.registerModules(SecurityJackson2Modules.getModules(loader));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Optional<Cookie> getCookie(HttpServletRequest request, String name) {
 | 
			
		||||
        Cookie[] cookies = request.getCookies();
 | 
			
		||||
 | 
			
		||||
@ -56,7 +64,7 @@ public class CookieUtils {
 | 
			
		||||
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
 | 
			
		||||
        Cookie[] cookies = request.getCookies();
 | 
			
		||||
        if (cookies != null && cookies.length > 0) {
 | 
			
		||||
            for (Cookie cookie: cookies) {
 | 
			
		||||
            for (Cookie cookie : cookies) {
 | 
			
		||||
                if (cookie.getName().equals(name)) {
 | 
			
		||||
                    cookie.setValue("");
 | 
			
		||||
                    cookie.setPath("/");
 | 
			
		||||
@ -68,27 +76,22 @@ public class CookieUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String serialize(Object object) {
 | 
			
		||||
        return Base64.getUrlEncoder()
 | 
			
		||||
                .encodeToString(SerializationUtils.serialize(object));
 | 
			
		||||
        try {
 | 
			
		||||
            return Base64.getUrlEncoder()
 | 
			
		||||
                    .encodeToString(OBJECT_MAPPER.writeValueAsBytes(object));
 | 
			
		||||
        } catch (JsonProcessingException e) {
 | 
			
		||||
            throw new IllegalArgumentException("The given Json object value: "
 | 
			
		||||
                    + object + " cannot be transformed to a String", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T> T deserialize(Cookie cookie, Class<T> cls) {
 | 
			
		||||
        byte[] decodedBytes = Base64.getUrlDecoder().decode(cookie.getValue());
 | 
			
		||||
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(decodedBytes)) {
 | 
			
		||||
            @Override
 | 
			
		||||
            protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
 | 
			
		||||
                String name = desc.getName();
 | 
			
		||||
                if (!cls.getName().equals(name)) {
 | 
			
		||||
                    throw new ClassNotFoundException("Class not allowed for deserialization: " + name);
 | 
			
		||||
                }
 | 
			
		||||
                return super.resolveClass(desc);
 | 
			
		||||
            }
 | 
			
		||||
        }) {
 | 
			
		||||
 | 
			
		||||
            return cls.cast(ois.readObject());
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.debug("Failed to deserialize class from cookie.", e.getCause());
 | 
			
		||||
            return null;
 | 
			
		||||
        try {
 | 
			
		||||
            return OBJECT_MAPPER.readValue(decodedBytes, cls);
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            throw new IllegalArgumentException("The given string value: "
 | 
			
		||||
                    + Arrays.toString(decodedBytes) + " cannot be transformed to Json object", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2023 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * 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 org.thingsboard.server.service.security.auth.oauth2;
 | 
			
		||||
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.mockito.Mockito;
 | 
			
		||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.Cookie;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME;
 | 
			
		||||
 | 
			
		||||
public class CookieUtilsTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void serializeDeserializeOAuth2AuthorizationRequestTest() {
 | 
			
		||||
        HttpCookieOAuth2AuthorizationRequestRepository cookieRequestRepo = new HttpCookieOAuth2AuthorizationRequestRepository();
 | 
			
		||||
        HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
 | 
			
		||||
 | 
			
		||||
        Map<String, Object> additionalParameters = new LinkedHashMap<>();
 | 
			
		||||
        additionalParameters.put("param1", "value1");
 | 
			
		||||
        additionalParameters.put("param2", "value2");
 | 
			
		||||
        var request = OAuth2AuthorizationRequest.authorizationCode()
 | 
			
		||||
                .authorizationUri("testUri").clientId("testId")
 | 
			
		||||
                .scope("read", "write")
 | 
			
		||||
                .additionalParameters(additionalParameters).build();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Cookie cookie = new Cookie(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(request));
 | 
			
		||||
        Mockito.when(servletRequest.getCookies()).thenReturn(new Cookie[]{cookie});
 | 
			
		||||
 | 
			
		||||
        OAuth2AuthorizationRequest deserializedRequest = cookieRequestRepo.loadAuthorizationRequest(servletRequest);
 | 
			
		||||
 | 
			
		||||
        assertNotNull(deserializedRequest);
 | 
			
		||||
        assertEquals(request.getGrantType(), deserializedRequest.getGrantType());
 | 
			
		||||
        assertEquals(request.getAuthorizationUri(), deserializedRequest.getAuthorizationUri());
 | 
			
		||||
        assertEquals(request.getClientId(), deserializedRequest.getClientId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,66 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2023 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * 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 org.thingsboard.server.service.security.auth.oauth2;
 | 
			
		||||
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.mockito.Mockito;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.Cookie;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.ObjectInputStream;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME;
 | 
			
		||||
 | 
			
		||||
public class HttpCookieOAuth2AuthorizationRequestRepositoryTest {
 | 
			
		||||
 | 
			
		||||
    private static final String SERIALIZED_ATTACK_STRING =
 | 
			
		||||
            "rO0ABXNyAHVvcmcudGhpbmdzYm9hcmQuc2VydmVyLnNlcnZpY2Uuc2VjdXJpdHkuYXV0aC5vYXV0aDIuSHR0cENvb2tpZU9BdXRoMkF1dGhvcml6YXRpb25SZXF1ZXN0UmVwb3NpdG9yeVRlc3QkTWFsaWNpb3VzQ2xhc3MAAAAAAAAAAAIAAHhw";
 | 
			
		||||
 | 
			
		||||
    private static int maliciousMethodInvocationCounter;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void resetInvocationCounter() {
 | 
			
		||||
        maliciousMethodInvocationCounter = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void whenLoadAuthorizationRequest_thenMaliciousMethodNotInvoked() {
 | 
			
		||||
        HttpCookieOAuth2AuthorizationRequestRepository cookieRequestRepo = new HttpCookieOAuth2AuthorizationRequestRepository();
 | 
			
		||||
        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
 | 
			
		||||
        Cookie cookie = new Cookie(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, SERIALIZED_ATTACK_STRING);
 | 
			
		||||
        Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
 | 
			
		||||
 | 
			
		||||
        cookieRequestRepo.loadAuthorizationRequest(request);
 | 
			
		||||
 | 
			
		||||
        assertEquals(0, maliciousMethodInvocationCounter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class MaliciousClass implements Serializable {
 | 
			
		||||
        private static final long serialVersionUID = 0L;
 | 
			
		||||
 | 
			
		||||
        public void maliciousMethod() {
 | 
			
		||||
            maliciousMethodInvocationCounter++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 | 
			
		||||
            maliciousMethod();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user