AbstractAccessTokenValidatorTest.java

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF 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.apache.cxf.rs.security.oauth2.services;

import java.util.Collections;
import java.util.List;

import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.core.MultivaluedMap;
import org.apache.cxf.jaxrs.ext.MessageContext;
import org.apache.cxf.rs.security.oauth2.common.AccessTokenValidation;
import org.apache.cxf.rs.security.oauth2.provider.AccessTokenValidator;
import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;

public class AbstractAccessTokenValidatorTest {

    @Test
    public void testValidationDataCacheReusesSuccessfulValidationWithinLifetime() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(true);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);

        AccessTokenValidation firstValidation = accessTokenValidator.getValidation("token");
        AccessTokenValidation secondValidation = accessTokenValidator.getValidation("token");

        assertSame(firstValidation, secondValidation);
        assertEquals(1, validator.getValidationCount());
    }

    @Test
    public void testValidationDataCacheLifetimeBoundsSuccessfulValidationReuse() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(true);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);
        accessTokenValidator.setValidationDataCacheLifetime(0L);

        accessTokenValidator.getValidation("token");
        accessTokenValidator.getValidation("token");

        assertEquals(2, validator.getValidationCount());
    }

    @Test
    public void testValidationDataCacheDoesNotReuseUnsuccessfulValidation() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(false);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);

        accessTokenValidator.getValidation("token");
        accessTokenValidator.getValidation("token");

        assertEquals(2, validator.getValidationCount());
    }

    @Test
    public void testValidationDataCacheReusesTokenExpiringWithinDefaultLifetime() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(true);
        validator.setTokenLifetime(30L);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);

        AccessTokenValidation firstValidation = accessTokenValidator.getValidation("token");
        AccessTokenValidation secondValidation = accessTokenValidator.getValidation("token");

        assertSame(firstValidation, secondValidation);
        assertEquals(1, validator.getValidationCount());
    }

    @Test
    public void testValidationDataCacheRejectsExpiredTokenBeforeCacheLifetime() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(true);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);

        AccessTokenValidation validation = accessTokenValidator.getValidation("token");
        validation.setTokenIssuedAt(OAuthUtils.getIssuedAt() - 2L);
        validation.setTokenLifetime(1L);

        assertThrows(NotAuthorizedException.class, () -> accessTokenValidator.getValidation("token"));
        accessTokenValidator.getValidation("token");

        assertEquals(2, validator.getValidationCount());
    }

    @Test
    public void testValidationDataCacheRevalidatesBeforeTokenExpires() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(true);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);
        accessTokenValidator.setValidationDataCacheLifetime(0L);

        accessTokenValidator.getValidation("token");
        accessTokenValidator.getValidation("token");

        assertEquals(2, validator.getValidationCount());
    }

    @Test
    public void testValidationDataCacheLifetimeBoundsTokenWithNoLifetime() {
        TestAccessTokenValidator validator = new TestAccessTokenValidator(true);
        validator.setTokenLifetime(0L);
        TestValidator accessTokenValidator = createAccessTokenValidator(validator);
        accessTokenValidator.setValidationDataCacheLifetime(0L);

        accessTokenValidator.getValidation("token");
        accessTokenValidator.getValidation("token");

        assertEquals(2, validator.getValidationCount());
    }

    private static TestValidator createAccessTokenValidator(TestAccessTokenValidator validator) {
        TestValidator accessTokenValidator = new TestValidator();
        accessTokenValidator.setTokenValidator(validator);
        accessTokenValidator.setMaxValidationDataCacheSize(10);
        return accessTokenValidator;
    }

    private static final class TestValidator extends AbstractAccessTokenValidator {
        private AccessTokenValidation getValidation(String authSchemeData) {
            return getAccessTokenValidation(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, authSchemeData, null);
        }
    }

    private static final class TestAccessTokenValidator implements AccessTokenValidator {
        private final boolean validationSuccessful;
        private long tokenLifetime = 3600L;
        private int validationCount;

        private TestAccessTokenValidator(boolean validationSuccessful) {
            this.validationSuccessful = validationSuccessful;
        }

        @Override
        public List<String> getSupportedAuthorizationSchemes() {
            return Collections.singletonList(OAuthConstants.BEARER_AUTHORIZATION_SCHEME);
        }

        @Override
        public AccessTokenValidation validateAccessToken(MessageContext mc,
                                                         String authScheme,
                                                         String authSchemeData,
                                                         MultivaluedMap<String, String> extraProps)
            throws OAuthServiceException {
            validationCount++;
            AccessTokenValidation validation = new AccessTokenValidation();
            validation.setInitialValidationSuccessful(validationSuccessful);
            validation.setTokenKey(authSchemeData);
            validation.setTokenIssuedAt(OAuthUtils.getIssuedAt());
            validation.setTokenLifetime(tokenLifetime);
            return validation;
        }

        private void setTokenLifetime(long tokenLifetime) {
            this.tokenLifetime = tokenLifetime;
        }

        private int getValidationCount() {
            return validationCount;
        }
    }
}