ApiTokenServiceImpl.java
/*
* Copyright 2023 Global Crop Diversity Trust
*
* 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 org.genesys.blocks.tokenauth.service.impl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.tokenauth.model.ApiToken;
import org.genesys.blocks.tokenauth.persistence.ApiTokenPersistence;
import org.genesys.blocks.tokenauth.service.ApiTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
/**
* API Token services
*/
@Service
@Slf4j
public class ApiTokenServiceImpl implements ApiTokenService {
@Value("${apitoken.salt:hellothere}")
private String tokenSalt;
@Autowired
private ApiTokenPersistence apiTokenPersistence;
@Override
@Cacheable(cacheNames = { "api.tokenauth.encoded" }, key = "#token", unless = "#result == null")
public String encodeToken(@NonNull String token) {
log.debug("Encoding token {}", token);
return Base64.encodeBase64String(DigestUtils.sha256(tokenSalt.concat(token)));
}
@Override
@Transactional(readOnly = true)
@PostAuthorize("hasRole('ADMINISTRATOR') || returnObject.sid.id == principal.id") // or hasPermission(#id, 'org.genesys.blocks.tokenauth.model.ApiToken', 'READ')")
public ApiToken loadById(Long id) {
return apiTokenPersistence.findById(id).orElse(null);
}
@Override
@Transactional(readOnly = true)
@Cacheable(cacheNames = { "api.tokenauth.tokens" }, key = "#encodedToken", unless = "#result == null")
public ApiToken getToken(String encodedToken) {
log.debug("Loading token from database {}", encodedToken);
return apiTokenPersistence.findByToken(encodedToken).orElse(null);
}
@Override
@Transactional(readOnly = true)
@PreAuthorize("hasRole('ADMINISTRATOR') || (hasRole('VETTEDUSER') && #sid.id == principal.id)") // Admins and vetted users can list tokens
public List<ApiToken> listTokensForSid(AclSid sid) {
return apiTokenPersistence.findAllBySid(sid);
}
@Override
@Transactional(readOnly = true)
@PreAuthorize("hasRole('ADMINISTRATOR')")
public Page<ApiToken> listTokens(Pageable page) {
return apiTokenPersistence.findAll(page);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') || (hasRole('VETTEDUSER') && #sid.id == principal.id)") // Admins and vetted users can create tokens
public ApiToken createToken(AclSid sid, String label, Instant expires) {
ApiToken apiToken = new ApiToken();
apiToken.setSid(sid);
apiToken.setExpires(expires);
apiToken.setLabel(label);
String token = null;
for (int i = 0; i < 10; i++) {
token = UUID.randomUUID().toString();
apiToken.setToken(encodeToken(token));
if (apiTokenPersistence.findByToken(apiToken.getToken()).isEmpty())
break;
log.info("Token already exists, generating a new one");
}
var saved = apiTokenPersistence.save(apiToken);
saved.setToken(token);
log.info("Created a new token for {} with label={} expires={}", apiToken.getSid().getSid(), apiToken.getLabel(), apiToken.getExpires());
return saved;
}
@Override
@Transactional
@PostAuthorize("hasRole('ADMINISTRATOR') || (hasRole('VETTEDUSER') && returnObject.sid.id == principal.id)")
@CacheEvict(cacheNames = { "api.tokenauth.tokens" }, key = "#result.token", condition = "#result != null")
public ApiToken remove(ApiToken apiToken) {
assert (apiToken != null && apiToken.getId() != null);
ApiToken token = apiTokenPersistence.findById(apiToken.getId()).orElse(null);
if (token == null) {
return null;
} else {
log.info("Deleting token for {} with label={}", token.getSid().getSid(), token.getLabel());
apiTokenPersistence.delete(token);
return token;
}
}
@Override
@Transactional
@PostAuthorize("hasRole('ADMINISTRATOR') || (hasRole('VETTEDUSER') && returnObject.sid.id == principal.id)")
@CacheEvict(cacheNames = { "api.tokenauth.tokens" }, key = "#result.token", condition = "#result != null")
public ApiToken update(ApiToken apiToken) {
ApiToken toUpdate = apiTokenPersistence.findById(apiToken.getId()).orElse(null);
if (toUpdate == null) {
return null;
}
log.info("Updating token for {} with label={}", toUpdate.getSid().getSid(), toUpdate.getLabel());
toUpdate.apply(apiToken);
return apiTokenPersistence.save(toUpdate);
}
}