All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.ydb.core.auth.StaticCredentials Maven / Gradle / Ivy

package tech.ydb.core.auth;

import java.time.Clock;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;

import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tech.ydb.auth.AuthRpcProvider;
import tech.ydb.core.impl.auth.GrpcAuthRpc;
import tech.ydb.proto.auth.YdbAuth;

/**
 *
 * @author Aleksandr Gorshenin
 */
public class StaticCredentials implements AuthRpcProvider {
    private static final Logger logger = LoggerFactory.getLogger(StaticCredentials.class);

    private final Clock clock;
    private final YdbAuth.LoginRequest request;

    @VisibleForTesting
    StaticCredentials(Clock clock, String username, String password) {
        this.clock = clock;
        YdbAuth.LoginRequest.Builder builder = YdbAuth.LoginRequest.newBuilder()
                .setUser(username);
        if (password != null) {
            builder.setPassword(password);
        }
        this.request = builder.build();
    }

    public StaticCredentials(String username, String password) {
        this(Clock.systemUTC(), username, password);
    }

    @Override
    public tech.ydb.auth.AuthIdentity createAuthIdentity(GrpcAuthRpc rpc) {
        logger.info("create static identity for database {}", rpc.getDatabase());
        return new IdentityImpl(rpc);
    }

    private interface State {
        void init();
        State validate(Instant now);
        String token();
    }

    private class IdentityImpl implements tech.ydb.auth.AuthIdentity {
        private final AtomicReference state = new AtomicReference<>(new NullState());
        private final StaticCredentialsRpc rpc;

        IdentityImpl(GrpcAuthRpc authRpc) {
            this.rpc = new StaticCredentialsRpc(authRpc, request, clock);
        }

        private State updateState(State current, State next) {
            if (state.compareAndSet(current, next)) {
                next.init();
            }
            return state.get();
        }

        @Override
        public String getToken() {
            return state.get().validate(clock.instant()).token();
        }

        private class NullState implements State {
            @Override
            public void init() {
                // Nothing
            }

            @Override
            public String token() {
                throw new IllegalStateException("Get token for null state");
            }

            @Override
            public State validate(Instant now) {
                return updateState(this, new SyncLogin()).validate(now);
            }
        }

        private class SyncLogin implements State {
            private final CompletableFuture future = new CompletableFuture<>();

            @Override
            public void init() {
                rpc.loginAsync().whenComplete((token, th) -> {
                    if (token != null) {
                        future.complete(new LoggedInState(token));
                    } else {
                        future.complete(new ErrorState(th));
                    }
                });
            }

            @Override
            public String token() {
                throw new IllegalStateException("Get token for unfinished sync state");
            }

            @Override
            public State validate(Instant now) {
                return updateState(this, rpc.unwrap(future));
            }
        }

        private class BackgroundLogin implements State {
            private final StaticCredentialsRpc.Token token;
            private final CompletableFuture future = new CompletableFuture<>();

            BackgroundLogin(StaticCredentialsRpc.Token token) {
                this.token = token;
            }

            @Override
            public void init() {
                rpc.loginAsync().whenComplete((nextToken, th) -> {
                    if (nextToken != null) {
                        future.complete(new LoggedInState(nextToken));
                    } else {
                        future.completeExceptionally(th);
                    }
                });
            }

            @Override
            public String token() {
                return token.token();
            }

            @Override
            public State validate(Instant now) {
                if (future.isCompletedExceptionally()) {
                    if (now.isAfter(token.expiredAt())) {
                        // If token had already expired, switch to sync mode and wait for finishing
                        return updateState(this, new SyncLogin()).validate(now);
                    }
                    // else retry background login
                    return updateState(this, new BackgroundLogin(token));
                }

                if (future.isDone()) {
                    return updateState(this, future.join());
                }

                return this;
            }
        }

        private class LoggedInState implements State {
            private final StaticCredentialsRpc.Token token;

            LoggedInState(StaticCredentialsRpc.Token token) {
                this.token = token;
                logger.debug("logged in with expired at {} and updating at {}", token.expiredAt(), token.updateAt());
            }

            @Override
            public void init() { }

            @Override
            public String token() {
                return token.token();
            }

            @Override
            public State validate(Instant now) {
                if (now.isAfter(token.expiredAt())) {
                    // If token had already expired, switch to sync mode and wait for finishing
                    return updateState(this, new SyncLogin()).validate(now);
                }
                if (now.isAfter(token.updateAt())) {
                    return updateState(this, new BackgroundLogin(token));
                }
                return this;
            }
        }

        private class ErrorState implements State {
            private final RuntimeException ex;

            ErrorState(Throwable ex) {
                this.ex = ex instanceof RuntimeException ?
                        (RuntimeException) ex : new RuntimeException("can't login", ex);
            }

            @Override
            public void init() { }

            @Override
            public String token() {
                throw ex;
            }

            @Override
            public State validate(Instant instant) {
                return this;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy