CreateTokenOperation.java

/*
 * Copyright 2017 RedRoma, 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 tech.aroma.authentication.service.operations;

import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;
import javax.inject.Inject;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.aroma.authentication.service.data.TokenCreator;
import tech.aroma.data.TokenRepository;
import tech.aroma.thrift.LengthOfTime;
import tech.aroma.thrift.TimeUnit;
import tech.aroma.thrift.authentication.AuthenticationToken;
import tech.aroma.thrift.authentication.TokenStatus;
import tech.aroma.thrift.authentication.service.CreateTokenRequest;
import tech.aroma.thrift.authentication.service.CreateTokenResponse;
import tech.aroma.thrift.exceptions.OperationFailedException;
import tech.sirwellington.alchemy.annotations.access.Internal;
import tech.sirwellington.alchemy.thrift.operations.ThriftOperation;

import static java.time.Instant.now;
import static tech.aroma.thrift.assertions.AromaAssertions.checkRequestNotNull;
import static tech.aroma.thrift.assertions.AromaAssertions.withMessage;
import static tech.sirwellington.alchemy.arguments.Arguments.checkThat;
import static tech.sirwellington.alchemy.arguments.assertions.Assertions.notNull;
import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.nonEmptyString;
import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.stringWithLengthGreaterThanOrEqualTo;
import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.validUUID;

/**
 *
 * @author SirWellington
 */
@Internal
final class CreateTokenOperation implements ThriftOperation<CreateTokenRequest, CreateTokenResponse>
{

    private final static Logger LOG = LoggerFactory.getLogger(CreateTokenOperation.class);
    private final static LengthOfTime DEFAULT_LIFETIME = new LengthOfTime(TimeUnit.DAYS, 90);

    private final Function<LengthOfTime, Duration> lengthOfTimeConverter;
    private final TokenCreator tokenCreator;
    private final TokenRepository repository;

    @Inject
    CreateTokenOperation(Function<LengthOfTime, Duration> lengthOfTimeConverter,
                         TokenCreator tokenCreator,
                         TokenRepository repository)
    {
        checkThat(lengthOfTimeConverter, tokenCreator, repository)
            .are(notNull());

        this.lengthOfTimeConverter = lengthOfTimeConverter;
        this.tokenCreator = tokenCreator;
        this.repository = repository;
    }

    @Override
    public CreateTokenResponse process(CreateTokenRequest request) throws TException
    {
        LOG.debug("Received request to create an  Token: {}", request);

        checkRequestNotNull(request);

        checkThat(request.desiredTokenType)
            .throwing(withMessage("Token Type is required"))
            .is(notNull());

        checkThat(request.ownerId)
            .throwing(withMessage("bad owner ID"))
            .is(nonEmptyString())
            .usingMessage("ownerId must be >= 3")
            .is(stringWithLengthGreaterThanOrEqualTo(3))
            .usingMessage("ownerId must be a UUID type")
            .is(validUUID());

        if (!request.isSetLifetime())
        {
            LOG.debug("Token Lifetime not set. Defaulting to {}", DEFAULT_LIFETIME);
            request.setLifetime(DEFAULT_LIFETIME);
        }

        String tokenId = tokenCreator.create();
        Instant timeOfCreation = now();
        checkThat(tokenId)
            .throwing(OperationFailedException.class)
            .is(nonEmptyString());

        Instant timeOfExpiration = determineExpirationTimeFrom(timeOfCreation, request);

        AuthenticationToken token = new AuthenticationToken()
            .setTokenId(tokenId)
            .setOwnerId(request.ownerId)
            .setOwnerName(request.ownerName)
            .setOrganizationId(request.organizationId)
            .setOrganizationName(request.ownerName)
            .setTokenType(request.desiredTokenType)
            .setTimeOfCreation(timeOfCreation.toEpochMilli())
            .setTimeOfExpiration(timeOfExpiration.toEpochMilli())
            .setStatus(TokenStatus.ACTIVE);

        repository.saveToken(token);
        LOG.debug("Saved token to repository: {}", token);

        return new CreateTokenResponse().setToken(token);
    }

    private Instant determineExpirationTimeFrom(Instant timeOfCreation, CreateTokenRequest request)
    {
        Duration tokenLifetime = lengthOfTimeConverter.apply(request.lifetime);

        Instant expirationTime = timeOfCreation.plus(tokenLifetime);

        return expirationTime;
    }

}