/*
 * Copyright 2019 ThoughtWorks, Inc.
 *
 * 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 com.thoughtworks.go.server.service;

import com.google.common.base.Ticker;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.thoughtworks.go.config.PluginRoleConfig;
import com.thoughtworks.go.config.SecurityAuthConfig;
import com.thoughtworks.go.plugin.access.authorization.AuthorizationExtension;
import com.thoughtworks.go.util.SystemEnvironment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class AuthorizationExtensionCacheService {
    private final int CACHE_EXPIRY_IN_MINUTES = SystemEnvironment.getGoServerAuthorizationExtensionCallsCacheTimeoutInSeconds() / 60;

    private final Cache<String, Boolean> isValidUserCache;
    private final Cache<String, List<String>> getUserRolesCache;
    private final AuthorizationExtension authorizationExtension;

    public AuthorizationExtensionCacheService(AuthorizationExtension authorizationExtension, Ticker ticker) {
        this.authorizationExtension = authorizationExtension;
        isValidUserCache = CacheBuilder.newBuilder()
                .ticker(ticker).expireAfterWrite(CACHE_EXPIRY_IN_MINUTES, TimeUnit.MINUTES).build();
        getUserRolesCache = CacheBuilder.newBuilder()
                .ticker(ticker).expireAfterWrite(CACHE_EXPIRY_IN_MINUTES, TimeUnit.MINUTES).build();
    }

    @Autowired
    public AuthorizationExtensionCacheService(AuthorizationExtension authorizationExtension) {
        this(authorizationExtension, Ticker.systemTicker());
    }

    public boolean isValidUser(String pluginId, String username, SecurityAuthConfig authConfig) {
        String cacheKey = cacheKeyFor(pluginId, username, authConfig);
        Boolean fromCache = isValidUserCache.getIfPresent(cacheKey);

        if (fromCache == null) {
            fromCache = authorizationExtension.isValidUser(pluginId, username, authConfig);
            isValidUserCache.put(cacheKey, fromCache);
        }

        return fromCache;
    }

    public List<String> getUserRoles(String pluginId, String username, SecurityAuthConfig authConfig, List<PluginRoleConfig> pluginRoleConfigs) {
        String cacheKey = cacheKeyFor(pluginId, username, authConfig, pluginRoleConfigs);
        List<String> rolesFromCache = getUserRolesCache.getIfPresent(cacheKey);

        if (rolesFromCache == null) {
            rolesFromCache = authorizationExtension.getUserRoles(pluginId, username, authConfig, pluginRoleConfigs);
            getUserRolesCache.put(cacheKey, rolesFromCache);
        }

        return rolesFromCache;
    }

    private String cacheKeyFor(String pluginId, String username, SecurityAuthConfig authConfig, List<PluginRoleConfig> pluginRoleConfigs) {
        String roleConfigNames = pluginRoleConfigs.stream().map(role -> role.getName().toString()).sorted().collect(Collectors.joining("&&"));
        return String.format("%s##%s", cacheKeyFor(pluginId, username, authConfig), roleConfigNames);
    }

    private String cacheKeyFor(String pluginId, String username, SecurityAuthConfig authConfig) {
        return String.format("%s##%s##%s", pluginId, username, authConfig.getId());
    }

    public void invalidateCache() {
        isValidUserCache.invalidateAll();
        getUserRolesCache.invalidateAll();
    }

}

@@ -947,16 +947,75 @@
 ension;%0A
+import com.thoughtworks.go.server.cache.CacheKeyGenerator;%0A
 import c
@@ -1057,16 +1057,16 @@
 onment;%0A
-
 import o
@@ -1652,16 +1652,71 @@
 tension;
+%0A    private final CacheKeyGenerator cacheKeyGenerator;
 %0A%0A    pu
@@ -2186,24 +2186,92 @@
 S).build();%0A
+        this.cacheKeyGenerator = new CacheKeyGenerator(getClass());%0A
     %7D%0A%0A    @
@@ -2567,36 +2567,89 @@
 heKey = cacheKey
-For(
+Generator.generate(%22AuthorizationExtension_isValidUser%22, 
 pluginId, userna
@@ -2654,32 +2654,40 @@
 name, authConfig
+.getId()
 );%0A        Boole
@@ -3132,31 +3132,228 @@
 ing 
-cacheKey = cacheKeyFor(
+roleConfigNames = pluginRoleConfigs.stream().map(role -%3E role.getName().toString()).sorted().collect(Collectors.joining(%22&&%22));%0A        String cacheKey = cacheKeyGenerator.generate(%22AuthorizationExtension_GetUserRoles%22, 
 plug
@@ -3374,33 +3374,35 @@
 , authConfig
-, pluginR
+.getId(), r
 oleConfigs);
@@ -3390,36 +3390,41 @@
 Id(), roleConfig
+Name
 s);%0A
+%0A
         List%3CStr
@@ -3707,32 +3707,32 @@
 he);%0A        %7D%0A%0A
+
         return r
@@ -3757,589 +3757,8 @@
  %7D%0A%0A
-    private String cacheKeyFor(String pluginId, String username, SecurityAuthConfig authConfig, List%3CPluginRoleConfig%3E pluginRoleConfigs) %7B%0A        String roleConfigNames = pluginRoleConfigs.stream().map(role -%3E role.getName().toString()).sorted().collect(Collectors.joining(%22&&%22));%0A        return String.format(%22%25s##%25s%22, cacheKeyFor(pluginId, username, authConfig), roleConfigNames);%0A    %7D%0A%0A    private String cacheKeyFor(String pluginId, String username, SecurityAuthConfig authConfig) %7B%0A        return String.format(%22%25s##%25s##%25s%22, pluginId, username, authConfig.getId());%0A    %7D%0A%0A
     

Use CacheKeyGenerator to generate AuthorizationExtensionCacheService cache keys (#5866)