Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TASK-4641- Implement an API key to control the number of queries #660

Merged
merged 15 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.beust.jcommander.*;
import org.opencb.cellbase.app.cli.CliOptionsParser;
import org.opencb.cellbase.core.api.key.ApiKeyQuota;

import java.util.HashMap;
import java.util.List;
Expand All @@ -34,7 +35,7 @@ public class AdminCliOptionsParser extends CliOptionsParser {
private DownloadCommandOptions downloadCommandOptions;
private BuildCommandOptions buildCommandOptions;
private DataReleaseCommandOptions dataReleaseCommandOptions;
private DataTokenCommandOptions dataTokenCommandOptions;
private ApiKeyCommandOptions apiKeyCommandOptions;
private LoadCommandOptions loadCommandOptions;
private ExportCommandOptions exportCommandOptions;
private CustomiseCommandOptions customiseCommandOptions;
Expand All @@ -51,7 +52,7 @@ public AdminCliOptionsParser() {
downloadCommandOptions = new DownloadCommandOptions();
buildCommandOptions = new BuildCommandOptions();
dataReleaseCommandOptions = new DataReleaseCommandOptions();
dataTokenCommandOptions = new DataTokenCommandOptions();
apiKeyCommandOptions = new ApiKeyCommandOptions();
loadCommandOptions = new LoadCommandOptions();
exportCommandOptions = new ExportCommandOptions();
customiseCommandOptions = new CustomiseCommandOptions();
Expand All @@ -63,7 +64,7 @@ public AdminCliOptionsParser() {
jCommander.addCommand("download", downloadCommandOptions);
jCommander.addCommand("build", buildCommandOptions);
jCommander.addCommand("data-release", dataReleaseCommandOptions);
jCommander.addCommand("data-token", dataTokenCommandOptions);
jCommander.addCommand("api-key", apiKeyCommandOptions);
jCommander.addCommand("load", loadCommandOptions);
jCommander.addCommand("export", exportCommandOptions);
jCommander.addCommand("customise", customiseCommandOptions);
Expand Down Expand Up @@ -152,20 +153,31 @@ public class DataReleaseCommandOptions {
public String versions;
}

@Parameters(commandNames = {"data-token"}, commandDescription = "Manage data access tokens in order to access to restricted/licensed data sources")
public class DataTokenCommandOptions {
@Parameters(commandNames = {"api-key"}, commandDescription = "Manage API keys in order to access to restricted/licensed data sources and set quota")
public class ApiKeyCommandOptions {

@ParametersDelegate
public CommonCommandOptions commonOptions = commonCommandOptions;

@Parameter(names = {"--create-token"}, description = "Create a data access token with the data sources indicated separated by commas and optionally the expiration date: source[:dd/mm/yyyy]. e.g.: cosmic:31/01/2025,hgmd. In addition the 'organization' has to be specified", arity = 1)
@Parameter(names = {"--create-api-key"}, description = "Create an API key with the licensed data sources indicated separated by"
+ " commas and optionally the expiration date: source[:dd/mm/yyyy]. e.g.: cosmic:31/01/2025,hgmd. In addition the"
+ " 'organization' has to be specified", arity = 1)
public String createWithDataSources;

@Parameter(names = {"--organization"}, description = "Organization (to be used with the --create-token parameter)", arity = 1)
@Parameter(names = {"--expiration"}, description = "Use this parameter in conjunction with --create-api-key to specify the"
+ " expiration date in format dd/mm/yyyy, e.g.: 03/09/2030", arity = 1)
public String expiration;

@Parameter(names = {"--organization"}, description = "Use this parameter in conjunction with --create-api-key to specify the"
+ " organization", arity = 1)
public String organization;

@Parameter(names = {"--view-token"}, description = "Token to view", arity = 1)
public String tokenToView;
@Parameter(names = {"--max-num-queries"}, description = "Use this parameter in conjunction with --create-api-key to specify the"
+ " maximum number of queries per month", arity = 1)
public long maxNumQueries = ApiKeyQuota.DEFAULT_MAX_NUM_QUERIES;

@Parameter(names = {"--view-api-key"}, description = "API key to view", arity = 1)
public String apiKeyToView;
}

@Parameters(commandNames = {"load"}, commandDescription = "Load the built data models into the database")
Expand Down Expand Up @@ -233,8 +245,8 @@ public class ExportCommandOptions {
@Parameter(names = {"--data-release"}, description = "Data release for exporting data.", required = true, arity = 1)
public int dataRelease;

@Parameter(names = {"--token"}, description = "Data token to export licensed data.", arity = 1)
public String token;
@Parameter(names = {"--api-key"}, description = "API key to export licensed data.", arity = 1)
public String apiKey;

@Parameter(names = {"--gene"}, description = "List of genes (separated by commas). Exported data will be related to these genes"
+ " (gene coordinates will be taken into account).", required = true, arity = 1)
Expand Down Expand Up @@ -341,8 +353,8 @@ public class ValidationCommandOptions {
@Parameter(names = {"--data-release"}, description = "Data release. To use the default data release, please, set this parameter to 0", required = false, arity = 1)
public int dataRelease = 0;

@Parameter(names = {"--token"}, description = "Data token to get access to licensed/restricted data sources such as COSMIC or HGMD", required = false, arity = 1)
public String token;
@Parameter(names = {"--api-key"}, description = "API key to get access to licensed/restricted data sources such as COSMIC or HGMD", required = false, arity = 1)
public String apiKey;

@Parameter(names = {"-i", "--input-file"}, description = "Full path to VCF", required = true, arity = 1)
public String inputFile;
Expand Down Expand Up @@ -393,9 +405,7 @@ public DataReleaseCommandOptions getDataReleaseCommandOptions() {
return dataReleaseCommandOptions;
}

public DataTokenCommandOptions getDataTokenCommandOptions() {
return dataTokenCommandOptions;
}
public ApiKeyCommandOptions getApiKeyCommandOptions() {return apiKeyCommandOptions; }

public LoadCommandOptions getLoadCommandOptions() { return loadCommandOptions; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public static void main(String[] args) {
case "data-release":
commandExecutor = new DataReleaseCommandExecutor(cliOptionsParser.getDataReleaseCommandOptions());
break;
case "data-token":
commandExecutor = new DataTokenCommandExecutor(cliOptionsParser.getDataTokenCommandOptions());
case "api-key":
commandExecutor = new ApiKeyCommandExecutor(cliOptionsParser.getApiKeyCommandOptions());
break;
case "load":
commandExecutor = new LoadCommandExecutor(cliOptionsParser.getLoadCommandOptions());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2015-2020 OpenCB
*
* 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.opencb.cellbase.app.cli.admin.executors;

import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.opencb.cellbase.app.cli.CommandExecutor;
import org.opencb.cellbase.app.cli.admin.AdminCliOptionsParser;
import org.opencb.cellbase.core.api.key.ApiKeyJwtPayload;
import org.opencb.cellbase.core.api.key.ApiKeyManager;
import org.opencb.cellbase.core.api.key.ApiKeyQuota;

import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class ApiKeyCommandExecutor extends CommandExecutor {

private AdminCliOptionsParser.ApiKeyCommandOptions apiKeyCommandOptions;

private DateFormat dateFormatter = new SimpleDateFormat("dd/MM/yyyy");


public ApiKeyCommandExecutor(AdminCliOptionsParser.ApiKeyCommandOptions apiKeyCommandOptions) {
super(apiKeyCommandOptions.commonOptions.logLevel, apiKeyCommandOptions.commonOptions.conf);

this.apiKeyCommandOptions = apiKeyCommandOptions;
}


/**
* Execute one of the selected actions according to the input parameters.
*/
public void execute() {
checkParameters();

Key key = new SecretKeySpec(Base64.getEncoder().encode(configuration.getSecretKey().getBytes(StandardCharsets.UTF_8)),
SignatureAlgorithm.HS256.getJcaName());
ApiKeyManager apiKeyManager = new ApiKeyManager(SignatureAlgorithm.HS256.getValue(), key);

try {
if (StringUtils.isNotEmpty(apiKeyCommandOptions.createWithDataSources)) {
// Create the API key JWT payload
ApiKeyJwtPayload payload = new ApiKeyJwtPayload();
payload.setSubject(apiKeyCommandOptions.organization);
payload.setVersion(ApiKeyJwtPayload.CURRENT_VERSION);
payload.setIssuedAt(new Date());
if (apiKeyCommandOptions.expiration != null) {
payload.setExpiration(parseDate(apiKeyCommandOptions.expiration));
}
payload.setSources(parseSources(apiKeyCommandOptions.createWithDataSources));
payload.setQuota(new ApiKeyQuota(apiKeyCommandOptions.maxNumQueries));

// Create API key
String apiKey = apiKeyManager.encode(payload);
System.out.println("API key generated:\n" + apiKey);
} else if (StringUtils.isNotEmpty(apiKeyCommandOptions.apiKeyToView)) {
// View API key
apiKeyManager.display(apiKeyCommandOptions.apiKeyToView);
}
} catch (ParseException e) {
e.printStackTrace();
}

}

private void checkParameters() {
if (StringUtils.isNotEmpty(apiKeyCommandOptions.createWithDataSources)
&& StringUtils.isNotEmpty(apiKeyCommandOptions.apiKeyToView)) {
throw new IllegalArgumentException("Please, select only one of these input parameters: create or view");
}
if (StringUtils.isEmpty(apiKeyCommandOptions.createWithDataSources)
&& StringUtils.isEmpty(apiKeyCommandOptions.apiKeyToView)) {
throw new IllegalArgumentException("Please, it is mandatory to select one of these input parameters: create or view");
}

// Check create parameters
if (StringUtils.isNotEmpty(apiKeyCommandOptions.createWithDataSources)) {
if (StringUtils.isEmpty(apiKeyCommandOptions.organization)) {
throw new IllegalArgumentException("Missing organization");
}
}

if (StringUtils.isEmpty(configuration.getSecretKey())) {
throw new IllegalArgumentException("Missing secret key in the CellBase configuration file.");
}
}

private Map<String, Date> parseSources(String sources) throws ParseException {
Map<String, Date> sourcesMap = new HashMap<>();
if (StringUtils.isNotEmpty(sources)) {
String[] split = sources.split(",");
for (String source : split) {
String[] splits = source.split(":");
if (splits.length == 1) {
sourcesMap.put(splits[0], parseDate("31/12/999999"));
} else {
sourcesMap.put(splits[0], parseDate(splits[1]));
}
}
}
return sourcesMap;
}

private Date parseDate(String date) throws ParseException {
return dateFormatter.parse(date);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class ExportCommandExecutor extends CommandExecutor {
private Path output;
private String[] dataToExport;
private int dataRelease;
private String token;
private String apiKey;

private String database;
private CellBaseManagerFactory managerFactory;
Expand All @@ -72,7 +72,7 @@ public ExportCommandExecutor(AdminCliOptionsParser.ExportCommandOptions exportCo
this.exportCommandOptions = exportCommandOptions;

this.dataRelease = exportCommandOptions.dataRelease;
this.token = exportCommandOptions.token;
this.apiKey = exportCommandOptions.apiKey;

this.output = Paths.get(exportCommandOptions.output);

Expand Down Expand Up @@ -429,7 +429,7 @@ private int exportClinicalVariantData(List<Region> regions) throws CellBaseExcep
ClinicalManager clinicalManager = managerFactory.getClinicalManager(species, assembly);
ClinicalVariantQuery query = new ClinicalVariantQuery();
query.setDataRelease(dataRelease);
query.setToken(token);
query.setApiKey(apiKey);
int counter = 0;
for (Region region : regions) {
query.setRegions(Collections.singletonList(region));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void execute() {
VariantAnnotationCalculator variantAnnotationCalculator = null;
try {
variantAnnotationCalculator = new VariantAnnotationCalculator(validationCommandOptions.species,
validationCommandOptions.assembly, validationCommandOptions.dataRelease, validationCommandOptions.token,
validationCommandOptions.assembly, validationCommandOptions.dataRelease, validationCommandOptions.apiKey,
cellBaseManagerFactory);
} catch (CellBaseException e) {
e.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ public class VariantAnnotationCommandOptions {
@Parameter(names = {"--data-release"}, description = "Data release. To use the default data relese, please, set this parameter to 0", required = false, arity = 1)
public int dataRelease = 0;

@Parameter(names = {"--token"}, description = "Data token to get access to licensed/restricted data sources such as COSMIC or HGMD", required = false, arity = 1)
public String token;
@Parameter(names = {"--api-key"}, description = "API key to get access to licensed/restricted data sources such as COSMIC or HGMD", required = false, arity = 1)
public String apiKey;

@Parameter(names = {"-l", "--local"}, description = "Database credentials for local annotation are read from configuration.json file", required = false, arity = 0)
public boolean local;
Expand Down
Loading
Loading