/*
 * Copyright 1999-2021 Alibaba Group Holding Ltd.
 *
 * 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.alibaba.nacos.plugin.auth.impl.token.impl;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.plugin.auth.impl.configuration.AuthConfigs;
import com.alibaba.nacos.common.event.ServerConfigChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.jwt.NacosJwtParser;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManager;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.sys.env.EnvUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;

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

/**
 * JWT token manager.
 *
 * @author wfnuser
 * @author nkorange
 */
public class JwtTokenManager extends Subscriber<ServerConfigChangeEvent> implements TokenManager {
    
    private static final String AUTH_DISABLED_TOKEN = "AUTH_DISABLED";
    
    /**
     * Token validity time(seconds).
     */
    private volatile long tokenValidityInSeconds;
    
    private volatile NacosJwtParser jwtParser;
    
    private final AuthConfigs authConfigs;
    
    public JwtTokenManager(AuthConfigs authConfigs) {
        this.authConfigs = authConfigs;
        NotifyCenter.registerSubscriber(this);
        processProperties();
    }
    
    private void processProperties() {
        this.tokenValidityInSeconds = EnvUtil.getProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, Long.class,
                AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS);
        
        String encodedSecretKey = EnvUtil.getProperty(AuthConstants.TOKEN_SECRET_KEY,
                AuthConstants.DEFAULT_TOKEN_SECRET_KEY);
        try {
            this.jwtParser = new NacosJwtParser(encodedSecretKey);
        } catch (Exception e) {
            this.jwtParser = null;
            if (authConfigs.isAuthEnabled() || authConfigs.isConsoleAuthEnabled()) {
                throw new IllegalArgumentException(
                        "the length of secret key must great than or equal 32 bytes; And the secret key  must be encoded by base64."
                                + "Please see https://nacos.io/docs/latest/manual/admin/auth/", e);
            }
        }
        
    }
    
    /**
     * Create token.
     *
     * @param authentication auth info
     * @return token
     */
    @Deprecated
    public String createToken(Authentication authentication) {
        return createToken(authentication.getName());
    }
    
    /**
     * Create token.
     *
     * @param userName auth info
     * @return token
     */
    public String createToken(String userName) {
        // create a token when auth enabled or nacos.core.auth.plugin.nacos.token.secret.key is configured
        if (!authConfigs.isAuthEnabled() && null == jwtParser) {
            return AUTH_DISABLED_TOKEN;
        } else if (authConfigs.isAuthEnabled()) {
            // check nacos.core.auth.plugin.nacos.token.secret.key only if auth enabled
            checkJwtParser();
        }
        return jwtParser.jwtBuilder().setUserName(userName).setExpiredTime(this.tokenValidityInSeconds).compact();
    }
    
    /**
     * Get auth Info.
     *
     * @param token token
     * @return auth info
     */
    @Deprecated
    public Authentication getAuthentication(String token) throws AccessException {
        NacosUser nacosUser = jwtParser.parse(token);
        
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.EMPTY);
        
        User principal = new User(nacosUser.getUserName(), "", authorities);
        return new UsernamePasswordAuthenticationToken(principal, "", authorities);
    }
    
    /**
     * validate token.
     *
     * @param token token
     */
    public void validateToken(String token) throws AccessException {
        parseToken(token);
    }
    
    public NacosUser parseToken(String token) throws AccessException {
        checkJwtParser();
        return jwtParser.parse(token);
    }
    
    public long getTokenValidityInSeconds() {
        return tokenValidityInSeconds;
    }
    
    @Override
    public long getTokenTtlInSeconds(String token) throws AccessException {
        if (!authConfigs.isAuthEnabled()) {
            return tokenValidityInSeconds;
        }
        return jwtParser.getExpireTimeInSeconds(token) - TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
    }
    
    public long getExpiredTimeInSeconds(String token) throws AccessException {
        if (!authConfigs.isAuthEnabled()) {
            return tokenValidityInSeconds;
        }
        return jwtParser.getExpireTimeInSeconds(token);
    }
    
    @Override
    public void onEvent(ServerConfigChangeEvent event) {
        processProperties();
    }
    
    @Override
    public Class<? extends Event> subscribeType() {
        return ServerConfigChangeEvent.class;
    }
    
    private void checkJwtParser() {
        if (null == jwtParser) {
            throw new NacosRuntimeException(NacosException.INVALID_PARAM,
                    "Please config `nacos.core.auth.plugin.nacos.token.secret.key`, detail see https://nacos.io/docs/latest/manual/admin/auth/");
        }
    }
}
