From b053bc3acbc8d0d84ea9bd0aa5612f462d2c1ab4 Mon Sep 17 00:00:00 2001 From: Antonis Kouzoupis Date: Thu, 21 Dec 2023 07:44:32 +0100 Subject: [PATCH] [HWORKS-743] Move API auth to separate module and add API key authentication to hopsworks-ca (#1438) * [HWORKS-743] [HWORKS-743] Move API auth to separate module [HWORKS-743] Annotate CA endpoints [HWORKS-743] Dedicated scope for PKI [HWORKS-743] Move InvalidQueryException to hopsworks-persistence package [HWORKS-743] Bump module version [HWORKS-743] Fix license * [HWORKS-743] Fixes for CE * [HWORKS-743] Remove empty files --- hopsworks-IT/src/test/ruby/spec/conf_spec.rb | 2 +- hopsworks-IT/src/test/ruby/spec/jwt_spec.rb | 82 ----- hopsworks-api-auth/pom.xml | 78 +++++ .../hopsworks/api/auth/Configuration.java | 80 +++++ .../api/auth}/HopsworksSecurityContext.java | 13 +- .../io/hops/hopsworks/api/auth/Secret.java | 142 +++++++++ .../io/hops/hopsworks/api/auth}/Subject.java | 12 +- .../api/auth}/UserStatusValidator.java | 31 +- .../hopsworks/api/auth/UserUtilities.java | 37 +++ .../hopsworks/api/auth/key}/ApiKeyFacade.java | 79 ++--- .../hopsworks/api/auth/key}/ApiKeyFilter.java | 92 +++--- .../api/auth/key}/ApiKeyRequired.java | 2 +- .../api/auth/key}/ApiKeyScopeFacade.java | 12 +- .../api/auth/key/ApiKeyUtilities.java | 63 ++++ hopsworks-api/pom.xml | 14 + .../api/admin/SystemAdminService.java | 21 -- .../api/admin/UsersAdminResource.java | 2 +- .../admin/cloud/CloudRoleMappingResource.java | 2 +- .../api/admin/conf/ConfigurationResource.java | 2 +- .../admin/projects/ProjectsAdminResource.java | 2 +- .../api/dataset/DatasetResource.java | 2 +- .../api/dataset/tags/DatasetTagsResource.java | 2 +- .../api/experiments/ExperimentsBuilder.java | 2 +- .../FeaturestoreKeywordResource.java | 4 +- .../api/featurestore/FeaturestoreService.java | 6 +- .../FsQueryConstructorResource.java | 2 +- .../activities/ActivityResource.java | 2 +- .../api/featurestore/code/CodeResource.java | 2 +- .../featurestore/commit/CommitResource.java | 2 +- .../alert/FeatureGroupAlertResource.java | 2 +- .../expectations/ExpectationBuilder.java | 4 +- .../expectations/ExpectationResource.java | 2 +- .../GreatExpectationBuilder.java | 5 +- .../GreatExpectationResource.java | 2 +- .../reports/ValidationReportBuilder.java | 4 +- .../reports/ValidationReportResource.java | 2 +- .../results/ValidationResultBuilder.java | 5 +- .../results/ValidationResultResource.java | 2 +- .../suites/ExpectationSuiteResource.java | 2 +- .../FeatureGroupPreviewResource.java | 2 +- .../featuregroup/FeaturegroupService.java | 2 +- .../featureview/FeatureViewResource.java | 2 +- .../PreparedStatementResource.java | 2 +- .../api/featurestore/query/QueryResource.java | 2 +- .../statistics/StatisticsResource.java | 2 +- .../FeaturestoreStorageConnectorService.java | 2 +- .../tag/FeatureStoreTagResource.java | 2 +- .../TrainingDatasetResource.java | 2 +- .../TrainingDatasetService.java | 2 +- .../TransformationResource.java | 2 +- .../TransformationFunctionResource.java | 2 +- .../hopsworks/api/filter/ApiKeyFilter.java | 83 +++++ .../hops/hopsworks/api/filter/AuthFilter.java | 8 +- .../hops/hopsworks/api/git/GitResource.java | 2 +- .../git/execution/GitExecutionResource.java | 2 +- .../hopsworks/api/jobs/FlinkProxyServlet.java | 8 +- .../hops/hopsworks/api/jobs/JobsResource.java | 2 +- .../api/jobs/alert/JobAlertsResource.java | 2 +- .../jobs/executions/ExecutionsResource.java | 2 +- .../jobs/scheduler/JobScheduleV2Resource.java | 2 +- .../io/hops/hopsworks/api/jwt/JWTHelper.java | 65 +--- .../hops/hopsworks/api/jwt/JWTResource.java | 77 +---- .../hopsworks/api/kafka/KafkaResource.java | 2 +- .../modelregistry/ModelRegistryResource.java | 2 +- .../ModelRegistryTagResource.java | 2 +- .../modelregistry/models/ModelsResource.java | 2 +- .../api/opensearch/OpenSearchService.java | 2 +- .../hopsworks/api/project/ProjectService.java | 2 +- .../project/alert/ProjectAlertsResource.java | 2 +- .../DefaultJobConfigurationResource.java | 2 +- .../provenance/ProjectProvenanceResource.java | 2 +- .../api/provenance/ProvenanceResource.java | 2 +- .../EnvironmentConflictsResource.java | 2 +- .../environment/EnvironmentResource.java | 2 +- .../command/EnvironmentCommandsResource.java | 2 +- .../EnvironmentCustomCommandsResource.java | 2 +- .../history/EnvironmentHistoryResource.java | 2 +- .../api/python/library/LibraryResource.java | 2 +- .../command/LibraryCommandsResource.java | 2 +- .../hopsworks/api/serving/ServingService.java | 2 +- .../serving/inference/InferenceResource.java | 11 +- .../api/tags/TagSchemasResource.java | 2 +- .../hops/hopsworks/api/user/AuthService.java | 30 +- .../hopsworks/api/user/UsersResource.java | 2 +- .../api/user/apiKey/ApiKeyBuilder.java | 2 +- .../api/user/apiKey/ApiKeyFilterBy.java | 2 +- .../api/user/apiKey/ApiKeyResource.java | 34 +- .../api/user/apiKey/ApiKeySortBy.java | 2 +- .../api/util/ClusterUtilisationService.java | 2 +- .../hopsworks/api/util/DownloadService.java | 2 +- .../hopsworks/api/util/UploadService.java | 2 +- .../hopsworks/api/util/VariablesService.java | 2 +- .../application/config/ApplicationConfig.java | 2 +- hopsworks-ca/pom.xml | 5 + .../hopsworks/ca/api/ApplicationConfig.java | 2 + .../ca/api/certificates/AppCertsResource.java | 4 + .../certificates/CertificatesResource.java | 3 + .../api/certificates/HostCertsResource.java | 5 + .../api/certificates/KubeCertsResource.java | 34 +- .../ca/api/certificates/PKIResource.java | 3 + .../certificates/ProjectCertsResource.java | 6 +- .../hopsworks/ca/api/filter/AuthFilter.java | 27 ++ .../ca/api/filter/CaApiKeyFilter.java | 84 +++++ hopsworks-common/pom.xml | 6 + .../hopsworks/common/dao/AbstractFacade.java | 286 +---------------- .../common/dao/hdfs/inode/InodeFacade.java | 2 +- .../dao/jobhistory/ExecutionFacade.java | 2 +- .../dao/jobs/description/JobFacade.java | 2 +- .../common/dao/kagent/HostServicesFacade.java | 2 +- .../hopsworks/common/dao/user/UserFacade.java | 2 +- .../expectations/ExpectationController.java | 5 +- .../reports/ValidationReportController.java | 9 +- .../results/ValidationResultController.java | 9 +- .../suites/ExpectationSuiteController.java | 4 +- .../cached/FeatureGroupCommitController.java | 6 +- .../hopsworks/common/git/GitJWTManager.java | 6 +- .../common/jupyter/JupyterJWTManager.java | 8 +- .../common/proxies/client/HttpClient.java | 5 +- .../common/security/ServiceJWTKeepAlive.java | 190 ----------- .../common/security/utils/Secret.java | 124 +------- .../hopsworks/common/user/AuthController.java | 1 + .../common/user/UsersController.java | 11 +- .../security/apiKey/ApiKeyController.java | 40 +-- .../hops/hopsworks/common/util/Settings.java | 64 +--- .../io/hops/hopsworks/jwt/JWTController.java | 65 ---- .../persistence}/InvalidQueryException.java | 4 +- .../entity/user/security/apiKey/ApiScope.java | 12 +- .../entity/util/AbstractFacade.java | 301 ++++++++++++++++++ hopsworks-rest-utils/pom.xml | 4 + .../restutils/RESTApiJsonResponse.java | 67 ++++ .../hopsworks/restutils/ThrowableMapper.java | 2 +- pom.xml | 7 + 132 files changed, 1368 insertions(+), 1281 deletions(-) create mode 100644 hopsworks-api-auth/pom.xml create mode 100644 hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Configuration.java rename {hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth}/HopsworksSecurityContext.java (96%) create mode 100644 hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Secret.java rename {hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth}/Subject.java (95%) rename {hopsworks-common/src/main/java/io/hops/hopsworks/common/user => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth}/UserStatusValidator.java (61%) create mode 100644 hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserUtilities.java rename {hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key}/ApiKeyFacade.java (89%) rename {hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key}/ApiKeyFilter.java (75%) rename {hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key}/ApiKeyRequired.java (96%) rename {hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey => hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key}/ApiKeyScopeFacade.java (86%) create mode 100644 hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyUtilities.java create mode 100644 hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/ApiKeyFilter.java create mode 100644 hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/CaApiKeyFilter.java delete mode 100644 hopsworks-common/src/main/java/io/hops/hopsworks/common/security/ServiceJWTKeepAlive.java rename {hopsworks-rest-utils/src/main/java/io/hops/hopsworks/exceptions => hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence}/InvalidQueryException.java (96%) create mode 100644 hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/util/AbstractFacade.java create mode 100644 hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/RESTApiJsonResponse.java diff --git a/hopsworks-IT/src/test/ruby/spec/conf_spec.rb b/hopsworks-IT/src/test/ruby/spec/conf_spec.rb index f7b5de4f7b..e8566eb49e 100644 --- a/hopsworks-IT/src/test/ruby/spec/conf_spec.rb +++ b/hopsworks-IT/src/test/ruby/spec/conf_spec.rb @@ -50,7 +50,7 @@ expect(conf_dto['items'].length > 0).to be true - hidden = conf_dto['items'].select {|c| c['name'].eql?("service_master_jwt")} + hidden = conf_dto['items'].select {|c| c['name'].eql?("int_service_api_key")} expect(hidden[0]['hide']).to be true end diff --git a/hopsworks-IT/src/test/ruby/spec/jwt_spec.rb b/hopsworks-IT/src/test/ruby/spec/jwt_spec.rb index 96316838da..69fac4c356 100644 --- a/hopsworks-IT/src/test/ruby/spec/jwt_spec.rb +++ b/hopsworks-IT/src/test/ruby/spec/jwt_spec.rb @@ -21,22 +21,6 @@ before :all do reset_session end - - context "#not logged in" do - it "should not be able to renew service JWT" do - put "#{ENV['HOPSWORKS_API']}/jwt/service", { - token: "some_token", - expiresAt: "1234", - nbf: "1234" - } - expect_status_details(401) - end - - it "should not be able to invalidate JWT" do - delete "#{ENV['HOPSWORKS_API']}/jwt/service/some_token" - expect_status_details(401) - end - end context "#users" do before :all do @@ -82,72 +66,6 @@ refresh_variables reset_session end - - it "should be able to renew master jwt" do - - now = Time.now - not_before = now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") - exp = now + 300 - new_expiration = exp.strftime("%Y-%m-%dT%H:%M:%S.%L%z") - - # Use one-time token - Airborne.configure do |config| - config.headers = {} - config.headers["Authorization"] = "Bearer #{@renew_tokens[0]}" - end - sleep 1 - put "#{ENV['HOPSWORKS_API']}/jwt/service", - { - token: @master_token, - expiresAt: new_expiration, - nbf: not_before - } - - expect_status_details(200) - - new_master_token = json_body[:jwt][:token] - new_one_time_tokens = json_body[:renewTokens] - expect(new_master_token).not_to be_nil - expect(new_master_token).not_to be_empty - - expect(new_one_time_tokens.length).to eql(5) - - master_jwt = JWT.decode new_master_token, nil, false - - exp_response = Time.at(master_jwt[0]['exp']) - nbf_response = Time.at(master_jwt[0]['nbf']) - # Do not compare milliseconds, there might be different due to conversion - expect(now.strftime("%Y-%m-%dT%H:%M:%S%z")).to eql(nbf_response.strftime("%Y-%m-%dT%H:%M:%S%z")) - expect(exp.strftime("%Y-%m-%dT%H:%M:%S%z")).to eql(exp_response.strftime("%Y-%m-%dT%H:%M:%S%z")) - - # Previous token should still be valid - Airborne.configure do |config| - config.headers["Authorization"] = "Bearer #{@master_token}" - end - get "#{ENV['HOPSWORKS_CA']}/token" - expect_status_details(200) - - # Invalidate previous master token - Airborne.configure do |config| - config.headers["Authorization"] = "Bearer #{new_master_token}" - end - delete "#{ENV['HOPSWORKS_API']}/jwt/service/#{@master_token}" - expect_status_details(200) - - # Subsequent calls with the old master key should fail - Airborne.configure do |config| - config.headers["Authorization"] = "Bearer #{@master_token}" - end - get "#{ENV['HOPSWORKS_CA']}/token" - expect_status_details(401) - - # But new master should be still valid... - Airborne.configure do |config| - config.headers["Authorization"] = "Bearer #{new_master_token}" - end - get "#{ENV['HOPSWORKS_CA']}/token" - expect_status_details(200) - end end end diff --git a/hopsworks-api-auth/pom.xml b/hopsworks-api-auth/pom.xml new file mode 100644 index 0000000000..434d252417 --- /dev/null +++ b/hopsworks-api-auth/pom.xml @@ -0,0 +1,78 @@ + + + + hopsworks + io.hops + 3.7.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + io.hops.hopsworks + hopsworks-api-auth + 3.7.0-SNAPSHOT + ejb + Hopsworks API authentication filters + hopsworks-api-auth + + + true + + + + + io.hops.hopsworks + hopsworks-persistence + + + io.hops.hopsworks + hopsworks-rest-utils + + + commons-codec + commons-codec + + + + + io.hops.hopsworks + hopsworks-jwt + ejb + provided + + + javax + javaee-api + + + commons-codec + commons-codec + 1.16.0 + + + + hopsworks-api-auth + + + org.apache.maven.plugins + maven-ejb-plugin + + + + \ No newline at end of file diff --git a/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Configuration.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Configuration.java new file mode 100644 index 0000000000..07f5c22b09 --- /dev/null +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Configuration.java @@ -0,0 +1,80 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ + +package io.hops.hopsworks.api.auth; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import io.hops.hopsworks.persistence.entity.util.Variables; +import io.hops.hopsworks.restutils.RESTLogLevel; + +import javax.annotation.PostConstruct; +import javax.ejb.ConcurrencyManagement; +import javax.ejb.ConcurrencyManagementType; +import javax.ejb.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +@Singleton +@ConcurrencyManagement(ConcurrencyManagementType.BEAN) +public class Configuration { + + @PersistenceContext(unitName = "kthfsPU") + private EntityManager em; + private LoadingCache configuration; + + public enum AuthConfigurationKeys { + + HOPSWORKS_REST_LOG_LEVEL("hopsworks_rest_log_level", RESTLogLevel.PROD.name()); + + private String key; + private String defaultValue; + + AuthConfigurationKeys(String key, String defaultValue) { + this.key = key; + this.defaultValue = defaultValue; + } + } + + @PostConstruct + public void init() { + configuration = CacheBuilder.newBuilder() + .maximumSize(100) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(new CacheLoader() { + @Override + public String load(AuthConfigurationKeys k) throws Exception { + Variables var = em.find(Variables.class, k.key); + return var != null ? var.getValue() : k.defaultValue; + } + }); + } + + private String get(AuthConfigurationKeys key) { + try { + return configuration.get(key); + } catch (ExecutionException ex) { + return key.defaultValue; + } + } + + public RESTLogLevel getLogLevel(AuthConfigurationKeys key) { + return RESTLogLevel.valueOf(get(key)); + } +} diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util/HopsworksSecurityContext.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/HopsworksSecurityContext.java similarity index 96% rename from hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util/HopsworksSecurityContext.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/HopsworksSecurityContext.java index 4c5137dd0d..a1739a4e86 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util/HopsworksSecurityContext.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/HopsworksSecurityContext.java @@ -13,21 +13,20 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.api.filter.util; +package io.hops.hopsworks.api.auth; import javax.ws.rs.core.SecurityContext; import java.security.Principal; public class HopsworksSecurityContext implements SecurityContext { - private final String scheme; private final Subject subject; - + public HopsworksSecurityContext(Subject subject, String scheme) { this.scheme = scheme; this.subject = subject; } - + @Override public Principal getUserPrincipal() { if (this.subject == null) { @@ -35,7 +34,7 @@ public Principal getUserPrincipal() { } return () -> this.subject.getName(); } - + @Override public boolean isUserInRole(String role) { if (this.subject.getRoles() != null && !this.subject.getRoles().isEmpty()) { @@ -43,12 +42,12 @@ public boolean isUserInRole(String role) { } return false; } - + @Override public boolean isSecure() { return "https".equals(this.scheme); } - + @Override public String getAuthenticationScheme() { return SecurityContext.BASIC_AUTH; diff --git a/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Secret.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Secret.java new file mode 100644 index 0000000000..a7c261e30b --- /dev/null +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Secret.java @@ -0,0 +1,142 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2019, Logical Clocks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ +package io.hops.hopsworks.api.auth; + +import org.apache.commons.codec.digest.DigestUtils; + +public class Secret { + + private static final String KEY_ID_SEPARATOR = "."; + public static final String KEY_ID_SEPARATOR_REGEX = "\\."; + private final String prefix; + private final String secret; + private final String salt; + private final int prefixMinLength; + private final int secretMinLength; + private final int saltMinLength; + private final boolean prefixed; + + public Secret(String prefix, String secret, String salt, int prefixMinLength, int secretMinLength, + int saltMinLength) { + this.prefix = prefix; + this.secret = secret; + this.salt = salt; + this.prefixMinLength = prefixMinLength; + this.secretMinLength = secretMinLength; + this.saltMinLength = saltMinLength; + this.prefixed = true; + } + + public Secret(String prefix, String secret, String salt) { + this.prefix = prefix; + this.secret = secret; + this.salt = salt; + this.prefixMinLength = 0; + this.secretMinLength = 0; + this.saltMinLength = 0; + this.prefixed = true; + } + + public Secret(String secret, String salt, int secretMinLength, int saltMinLength) { + this.prefix = ""; + this.secret = secret; + this.salt = salt; + this.prefixMinLength = 0; + this.secretMinLength = secretMinLength; + this.saltMinLength = saltMinLength; + this.prefixed = false; + } + + public Secret(String secret, String salt) { + this.prefix = ""; + this.secret = secret; + this.salt = salt; + this.prefixMinLength = 0; + this.secretMinLength = 0; + this.saltMinLength = 0; + this.prefixed = false; + } + + public String getPrefix() { + return prefix; + } + + public String getSecret() { + return secret; + } + + public String getSalt() { + return salt; + } + + public String getSecretPlusSalt() { + return this.secret + this.salt; + } + + public int getPrefixMinLength() { + return prefixMinLength; + } + + public int getSecretMinLength() { + return secretMinLength; + } + + public int getSaltMinLength() { + return saltMinLength; + } + + public boolean isPrefixed() { + return prefixed; + } + + public String getPrefixPlusSecret() { + return this.prefix + KEY_ID_SEPARATOR + this.secret; + } + + public String getSha256HexDigest() { + String secPlusSalt = getSecretPlusSalt(); + return DigestUtils.sha256Hex(secPlusSalt); + } + + public String getSha512HexDigest() { + String secPlusSalt = getSecretPlusSalt(); + return DigestUtils.sha512Hex(secPlusSalt); + } + + public String getSha1HexDigest() { + String secPlusSalt = getSecretPlusSalt(); + return DigestUtils.sha1Hex(secPlusSalt); + } + + public boolean validateNotNullOrEmpty() { + if (this.prefixed) { + return this.prefix != null && !this.prefix.isEmpty() && this.secret != null && !this.secret.isEmpty() && + this.salt != null && !this.salt.isEmpty(); + } else { + return this.secret != null && !this.secret.isEmpty() && this.salt != null && !this.salt.isEmpty(); + } + } + + public boolean validateSize() { + boolean notNullOrEmpty = validateNotNullOrEmpty(); + if (this.prefixed) { + return notNullOrEmpty && !(this.prefix.length() < prefixMinLength || this.secret.length() < secretMinLength || + this.salt.length() < saltMinLength); + } else { + return notNullOrEmpty && !(this.secret.length() < secretMinLength || this.salt.length() < saltMinLength); + } + } +} diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util/Subject.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Subject.java similarity index 95% rename from hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util/Subject.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Subject.java index 458ff8d45a..e752d66d39 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/util/Subject.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/Subject.java @@ -13,31 +13,31 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.api.filter.util; +package io.hops.hopsworks.api.auth; import java.util.List; public class Subject { private String name; private List roles; - + public Subject(String name, List roles) { this.name = name; this.roles = roles; } - + public String getName() { return name; } - + public void setName(String name) { this.name = name; } - + public List getRoles() { return roles; } - + public void setRoles(List roles) { this.roles = roles; } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UserStatusValidator.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserStatusValidator.java similarity index 61% rename from hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UserStatusValidator.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserStatusValidator.java index f87ad9f717..5b82ee973b 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UserStatusValidator.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserStatusValidator.java @@ -1,9 +1,6 @@ /* - * Changes to this file committed after and not including commit-id: ccc0d2c5f9a5ac661e60e6eaf138de7889928b8b - * are released under the following license: - * * This file is part of Hopsworks - * Copyright (C) 2018, Logical Clocks AB. All rights reserved + * Copyright (C) 2023, Hopsworks AB. All rights reserved * * Hopsworks is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, @@ -15,33 +12,13 @@ * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . - * - * Changes to this file committed before and including commit-id: ccc0d2c5f9a5ac661e60e6eaf138de7889928b8b - * are released under the following license: - * - * Copyright (C) 2013 - 2018, Logical Clocks AB and RISE SICS AB. All rights reserved - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, and to permit - * persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING - * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package io.hops.hopsworks.common.user; +package io.hops.hopsworks.api.auth; +import io.hops.hopsworks.exceptions.UserException; import io.hops.hopsworks.persistence.entity.user.security.ua.UserAccountStatus; import io.hops.hopsworks.restutils.RESTCodes; -import io.hops.hopsworks.exceptions.UserException; import javax.ejb.Stateless; import java.util.logging.Level; @@ -67,7 +44,7 @@ public boolean checkStatus(UserAccountStatus status) throws UserException { } return true; } - + public void checkNewUserStatus(UserAccountStatus status) throws UserException { switch (status) { case NEW_MOBILE_ACCOUNT: diff --git a/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserUtilities.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserUtilities.java new file mode 100644 index 0000000000..02f8abc5b3 --- /dev/null +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/UserUtilities.java @@ -0,0 +1,37 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ + +package io.hops.hopsworks.api.auth; + +import io.hops.hopsworks.persistence.entity.user.BbcGroup; +import io.hops.hopsworks.persistence.entity.user.Users; + +import javax.ejb.Stateless; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Stateless +public class UserUtilities { + public List getUserRoles(Users p) { + Collection groupList = p.getBbcGroupCollection(); + List list = new ArrayList<>(); + for (BbcGroup g : groupList) { + list.add(g.getGroupName()); + } + return list; + } +} diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey/ApiKeyFacade.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyFacade.java similarity index 89% rename from hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey/ApiKeyFacade.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyFacade.java index 98e889668c..5de97cfaf4 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey/ApiKeyFacade.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyFacade.java @@ -1,6 +1,6 @@ /* * This file is part of Hopsworks - * Copyright (C) 2019, Logical Clocks AB. All rights reserved + * Copyright (C) 2023, Hopsworks AB. All rights reserved * * Hopsworks is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, @@ -13,11 +13,11 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.common.dao.user.security.apiKey; +package io.hops.hopsworks.api.auth.key; -import io.hops.hopsworks.common.dao.AbstractFacade; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import javax.ejb.Stateless; import javax.persistence.EntityManager; @@ -31,64 +31,64 @@ @Stateless public class ApiKeyFacade extends AbstractFacade { - + @PersistenceContext(unitName = "kthfsPU") private EntityManager em; - + public ApiKeyFacade() { super(ApiKey.class); } - + @Override protected EntityManager getEntityManager() { return em; } - - + + public ApiKey findByPrefix(String prefix) { TypedQuery query = em.createNamedQuery("ApiKey.findByPrefix", - ApiKey.class).setParameter("prefix", prefix); + ApiKey.class).setParameter("prefix", prefix); try { return query.getSingleResult(); } catch (NoResultException e) { return null; } } - + public ApiKey findByUserAndName(Users user, String name) { TypedQuery query = em.createNamedQuery("ApiKey.findByUserAndName", - ApiKey.class).setParameter("user", user).setParameter("name", name); + ApiKey.class).setParameter("user", user).setParameter("name", name); try { return query.getSingleResult(); } catch (NoResultException e) { return null; } } - + public List findByUser(Users user) { TypedQuery query = em.createNamedQuery("ApiKey.findByUser", - ApiKey.class).setParameter("user", user); + ApiKey.class).setParameter("user", user); return query.getResultList(); } - + public CollectionInfo findByUser(Integer offset, Integer limit, Set filter, - Set sort, Users user) { + Set sort, Users user) { String queryStr = buildQuery("SELECT a FROM ApiKey a ", filter, sort, "a.user = :user AND a.reserved = 0"); String queryCountStr = buildQuery("SELECT COUNT(a.id) FROM ApiKey a ", filter, sort, "a.user = :user " + - "AND a.reserved = 0"); + "AND a.reserved = 0"); Query query = em.createQuery(queryStr, ApiKey.class).setParameter("user", user); Query queryCount = em.createQuery(queryCountStr, ApiKey.class).setParameter("user", user); return findAll(offset, limit, filter, query, queryCount); } - + private CollectionInfo findAll(Integer offset, Integer limit, - Set filter, Query query, Query queryCount) { + Set filter, Query query, Query queryCount) { setFilter(filter, query); setFilter(filter, queryCount); setOffsetAndLim(offset, limit, query); return new CollectionInfo((Long) queryCount.getSingleResult(), query.getResultList()); } - + private void setFilter(Set filters, Query q) { if (filters == null || filters.isEmpty()) { return; @@ -97,7 +97,7 @@ private void setFilter(Set filters, Query q) setFilterQuery(filter, q); } } - + private void setFilterQuery(FilterBy filter, Query q) { switch (Filters.valueOf(filter.getValue())) { case NAME: @@ -114,48 +114,48 @@ private void setFilterQuery(FilterBy filter, Query q) { break; default: break; - + } } - + public enum Sorts { NAME("NAME", "a.name ", "ASC"), MODIFIED("MODIFIED", "a.modified ", "ASC"), CREATED("CREATED", "a.created ", "ASC"); - + private final String value; private final String sql; private final String defaultParam; - + private Sorts(String value, String sql, String defaultParam) { this.value = value; this.sql = sql; this.defaultParam = defaultParam; } - + public String getValue() { return value; } - + public String getSql() { return sql; } - + public String getDefaultParam() { return defaultParam; } - + public String getJoin() { return null; } - + @Override public String toString() { return value; } - + } - + public enum Filters { NAME("NAME", "a.name IN :name ", "name", " "), MODIFIED("MODIFIED", "a.modified = :modified ", "modified", "1970-01-01T00:00:00.000"), @@ -164,39 +164,40 @@ public enum Filters { CREATED("CREATED", "a.created = :created ", "created", "1970-01-01T00:00:00.000"), CREATED_LT("CREATED_LT", "a.created < :created_lt ", "created_lt", "1970-01-01T00:00:00.000"), CREATED_GT("CREATED_GT", "a.created > :created_gt ", "created_gt", "1970-01-01T00:00:00.000"); - + private final String value; private final String sql; private final String field; private final String defaultParam; - + private Filters(String value, String sql, String field, String defaultParam) { this.value = value; this.sql = sql; this.field = field; this.defaultParam = defaultParam; } - + public String getDefaultParam() { return defaultParam; } - + public String getValue() { return value; } - + public String getSql() { return sql; } - + public String getField() { return field; } - + @Override public String toString() { return value; } - + } } + diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey/ApiKeyFilter.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyFilter.java similarity index 75% rename from hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey/ApiKeyFilter.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyFilter.java index 3ceeb3e926..846cbce6ec 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey/ApiKeyFilter.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyFilter.java @@ -1,6 +1,6 @@ /* * This file is part of Hopsworks - * Copyright (C) 2019, Logical Clocks AB. All rights reserved + * Copyright (C) 2023, Hopsworks AB. All rights reserved * * Hopsworks is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, @@ -13,15 +13,10 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.api.filter.apiKey; +package io.hops.hopsworks.api.auth.key; -import io.hops.hopsworks.api.filter.util.HopsworksSecurityContext; -import io.hops.hopsworks.api.filter.util.Subject; -import io.hops.hopsworks.api.util.RESTApiJsonResponse; -import io.hops.hopsworks.common.user.UserStatusValidator; -import io.hops.hopsworks.common.user.UsersController; -import io.hops.hopsworks.common.user.security.apiKey.ApiKeyController; -import io.hops.hopsworks.common.util.Settings; +import io.hops.hopsworks.api.auth.HopsworksSecurityContext; +import io.hops.hopsworks.api.auth.Subject; import io.hops.hopsworks.exceptions.ApiKeyException; import io.hops.hopsworks.exceptions.UserException; import io.hops.hopsworks.jwt.annotation.JWTRequired; @@ -29,18 +24,14 @@ import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.hops.hopsworks.restutils.JsonResponse; +import io.hops.hopsworks.restutils.RESTApiJsonResponse; import io.hops.hopsworks.restutils.RESTCodes; +import io.hops.hopsworks.restutils.RESTLogLevel; -import javax.annotation.Priority; -import javax.ejb.EJB; -import javax.ws.rs.Priorities; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; -import javax.ws.rs.ext.Provider; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; @@ -53,27 +44,11 @@ import static io.hops.hopsworks.jwt.Constants.BEARER; import static io.hops.hopsworks.jwt.Constants.WWW_AUTHENTICATE_VALUE; -@Provider -@ApiKeyRequired -@Priority(Priorities.AUTHENTICATION - 1) -public class ApiKeyFilter implements ContainerRequestFilter { - private static final Logger LOGGER = Logger.getLogger(ApiKeyFilter.class.getName()); - private static final String WWW_AUTHENTICATE_VALUE = "ApiKey realm=\"Cauth Realm\""; +public abstract class ApiKeyFilter implements ContainerRequestFilter { + private static final Logger LOGGER = Logger.getLogger(ApiKeyFilter.class.getName()); public static final String API_KEY = "ApiKey "; - - @EJB - private ApiKeyController apiKeyController; - @EJB - private UsersController usersController; - @EJB - private UserStatusValidator userStatusValidator; - @EJB - private Settings settings; - - @Context - private ResourceInfo resourceInfo; - + @Override public void filter(ContainerRequestContext requestContext) { String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); @@ -83,7 +58,7 @@ public void filter(ContainerRequestContext requestContext) { jsonResponse.setErrorCode(RESTCodes.SecurityErrorCode.EJB_ACCESS_LOCAL.getCode()); jsonResponse.setErrorMsg("Authorization header not set."); requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, - WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); + WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); return; } if (authorizationHeader.startsWith(BEARER)) { @@ -92,7 +67,7 @@ public void filter(ContainerRequestContext requestContext) { jsonResponse.setErrorCode(RESTCodes.SecurityErrorCode.EJB_ACCESS_LOCAL.getCode()); jsonResponse.setErrorMsg("Authorization method not supported."); requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, - WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); + WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); } return; } @@ -101,17 +76,17 @@ public void filter(ContainerRequestContext requestContext) { jsonResponse.setErrorCode(RESTCodes.SecurityErrorCode.EJB_ACCESS_LOCAL.getCode()); jsonResponse.setErrorMsg("Invalidated Api key."); requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, - WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); + WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); return; } - + String key = authorizationHeader.substring(API_KEY.length()).trim(); try { - ApiKey apiKey = apiKeyController.getApiKey(key); + ApiKey apiKey = getApiKey(key); Users user = apiKey.getUser(); - userStatusValidator.checkStatus(user.getStatus()); - List roles = usersController.getUserRoles(user); - Set scopes = apiKeyController.getScopes(apiKey); + validateUserStatus(user); + List roles = getUserRoles(user); + Set scopes = getApiScopes(apiKey); checkRole(roles); checkScope(scopes); Subject subject = new Subject(user.getUsername(), roles); @@ -119,13 +94,20 @@ public void filter(ContainerRequestContext requestContext) { requestContext.setSecurityContext(new HopsworksSecurityContext(subject, scheme)); } catch (ApiKeyException | UserException e) { LOGGER.log(Level.FINEST, "Api key Verification Exception: {0}", e.getMessage()); - e.buildJsonResponse(jsonResponse, settings.getHopsworksRESTLogLevel()); + e.buildJsonResponse(jsonResponse, getRestLogLevel()); requestContext.abortWith(Response.status(e.getErrorCode().getRespStatus().getStatusCode()) - .header(HttpHeaders.WWW_AUTHENTICATE, WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); + .header(HttpHeaders.WWW_AUTHENTICATE, WWW_AUTHENTICATE_VALUE).entity(jsonResponse).build()); } } - - + + protected abstract void validateUserStatus(Users user) throws UserException; + protected abstract ApiKey getApiKey(String key) throws ApiKeyException; + protected abstract Set getApiScopes(ApiKey key); + protected abstract List getUserRoles(Users user); + protected abstract RESTLogLevel getRestLogLevel(); + protected abstract Class getResourceClass(); + protected abstract Method getResourceMethod(); + private void checkRole(List userRoles) throws ApiKeyException { Set annotationRoles = getAllowedRoles(); if (annotationRoles.isEmpty() || userRoles == null || userRoles.isEmpty()) { @@ -136,7 +118,7 @@ private void checkRole(List userRoles) throws ApiKeyException { throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_ROLE_CONTROL_EXCEPTION, Level.FINE); } } - + private void checkScope(Set scopes) throws ApiKeyException { Set annotationScopes = getAllowedScopes(); if (annotationScopes.isEmpty() || scopes == null || scopes.isEmpty()) { @@ -147,7 +129,7 @@ private void checkScope(Set scopes) throws ApiKeyException { throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_SCOPE_CONTROL_EXCEPTION, Level.FINE); } } - + private Set getAllowedRoles() { ApiKeyRequired rolesAnnotation = getAnnotation(); if (rolesAnnotation == null) { @@ -155,7 +137,7 @@ private Set getAllowedRoles() { } return new HashSet<>(Arrays.asList(rolesAnnotation.allowedUserRoles())); } - + private Set getAllowedScopes() { ApiKeyRequired scopeAnnotation = getAnnotation(); if (scopeAnnotation == null) { @@ -163,18 +145,18 @@ private Set getAllowedScopes() { } return new HashSet<>(Arrays.asList(scopeAnnotation.acceptedScopes())); } - + private ApiKeyRequired getAnnotation() { - Class resourceClass = resourceInfo.getResourceClass(); - Method method = resourceInfo.getResourceMethod(); + Class resourceClass = getResourceClass(); + Method method = getResourceMethod(); ApiKeyRequired methodRolesAnnotation = method.getAnnotation(ApiKeyRequired.class); ApiKeyRequired classRolesAnnotation = resourceClass.getAnnotation(ApiKeyRequired.class); return methodRolesAnnotation != null ? methodRolesAnnotation : classRolesAnnotation; } - + private JWTRequired getJWTAnnotation() { - Class resourceClass = resourceInfo.getResourceClass(); - Method method = resourceInfo.getResourceMethod(); + Class resourceClass = getResourceClass(); + Method method = getResourceMethod(); JWTRequired methodAcceptedTokens = method.getAnnotation(JWTRequired.class); JWTRequired classAcceptedTokens = resourceClass.getAnnotation(JWTRequired.class); JWTRequired annotation = methodAcceptedTokens != null ? methodAcceptedTokens : classAcceptedTokens; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey/ApiKeyRequired.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyRequired.java similarity index 96% rename from hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey/ApiKeyRequired.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyRequired.java index b69584b8d5..6fce6ffb5d 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/apiKey/ApiKeyRequired.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyRequired.java @@ -13,7 +13,7 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.api.filter.apiKey; +package io.hops.hopsworks.api.auth.key; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey/ApiKeyScopeFacade.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyScopeFacade.java similarity index 86% rename from hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey/ApiKeyScopeFacade.java rename to hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyScopeFacade.java index 1fcb0ade20..4970ab41c3 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/security/apiKey/ApiKeyScopeFacade.java +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyScopeFacade.java @@ -1,6 +1,6 @@ /* * This file is part of Hopsworks - * Copyright (C) 2019, Logical Clocks AB. All rights reserved + * Copyright (C) 2023, Hopsworks AB. All rights reserved * * Hopsworks is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, @@ -13,10 +13,10 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.common.dao.user.security.apiKey; +package io.hops.hopsworks.api.auth.key; -import io.hops.hopsworks.common.dao.AbstractFacade; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKeyScope; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import javax.ejb.Stateless; import javax.persistence.EntityManager; @@ -24,17 +24,15 @@ @Stateless public class ApiKeyScopeFacade extends AbstractFacade { - @PersistenceContext(unitName = "kthfsPU") private EntityManager em; - + public ApiKeyScopeFacade() { super(ApiKeyScope.class); } - + @Override protected EntityManager getEntityManager() { return em; } - } diff --git a/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyUtilities.java b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyUtilities.java new file mode 100644 index 0000000000..cdb3c41884 --- /dev/null +++ b/hopsworks-api-auth/src/main/java/io/hops/hopsworks/api/auth/key/ApiKeyUtilities.java @@ -0,0 +1,63 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ +package io.hops.hopsworks.api.auth.key; + +import io.hops.hopsworks.api.auth.Secret; +import io.hops.hopsworks.exceptions.ApiKeyException; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKeyScope; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; +import io.hops.hopsworks.restutils.RESTCodes; + +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; + +@Stateless +@TransactionAttribute(TransactionAttributeType.NEVER) +public class ApiKeyUtilities { + @EJB + private ApiKeyFacade apiKeyFacade; + + public ApiKey getApiKey(String key) throws ApiKeyException { + String[] parts = key.split(Secret.KEY_ID_SEPARATOR_REGEX); + if (parts.length < 2) { + throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_INVALID, Level.FINE); + } + ApiKey apiKey = apiKeyFacade.findByPrefix(parts[0]); + if (apiKey == null) { + throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_NOT_FOUND_IN_DATABASE, Level.FINE); + } + //___MinLength can be set to 0 b/c no validation is needed if the key was in db + Secret secret = new Secret(parts[0], parts[1], apiKey.getSalt()); + if (!secret.getSha256HexDigest().equals(apiKey.getSecret())) { + throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_INVALID, Level.FINE); + } + return apiKey; + } + + public Set getScopes(ApiKey apiKey) { + Set scopes = new HashSet<>(); + for (ApiKeyScope scope : apiKey.getApiKeyScopeCollection()) { + scopes.add(scope.getScope()); + } + return scopes; + } +} diff --git a/hopsworks-api/pom.xml b/hopsworks-api/pom.xml index 2fb8a1d0f6..1c48632be4 100644 --- a/hopsworks-api/pom.xml +++ b/hopsworks-api/pom.xml @@ -113,6 +113,20 @@ provided + + io.hops.hopsworks + hopsworks-api-auth + ejb + + + + * + * + + + provided + + com.fasterxml.jackson.datatype diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java index bc1504e671..915d318266 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java @@ -51,7 +51,6 @@ import io.hops.hopsworks.common.dao.kafka.TopicDefaultValueDTO; import io.hops.hopsworks.common.kafka.KafkaController; import io.hops.hopsworks.common.security.CertificatesMgmService; -import io.hops.hopsworks.common.security.ServiceJWTKeepAlive; import io.hops.hopsworks.common.util.Settings; import io.hops.hopsworks.exceptions.FeaturestoreException; import io.hops.hopsworks.exceptions.OpenSearchException; @@ -59,7 +58,6 @@ import io.hops.hopsworks.exceptions.HopsSecurityException; import io.hops.hopsworks.exceptions.KafkaException; import io.hops.hopsworks.jwt.annotation.JWTRequired; -import io.hops.hopsworks.jwt.exception.JWTException; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.util.Variables; import io.hops.hopsworks.restutils.RESTCodes; @@ -108,8 +106,6 @@ public class SystemAdminService { @EJB private JWTHelper jWTHelper; @EJB - private ServiceJWTKeepAlive serviceJWTKeepAlive; - @EJB private KafkaController kafkaController; @EJB private SearchFSReindexer searchFSReindexer; @@ -193,23 +189,6 @@ public Response updateVariables(VariablesRequest variablesRequest, @Context Secu RESTApiJsonResponse response = noCacheResponse.buildJsonResponse(Response.Status.NO_CONTENT, "Variables updated"); return noCacheResponse.getNoCacheResponseBuilder(Response.Status.OK).entity(response).build(); } - - @POST - @Path("/rotate") - public Response serviceKeyRotate(@Context SecurityContext sc) { - certificatesMgmService.issueServiceKeyRotationCommand(); - RESTApiJsonResponse - response = noCacheResponse.buildJsonResponse(Response.Status.NO_CONTENT, "Key rotation commands " + - "issued"); - return noCacheResponse.getNoCacheResponseBuilder(Response.Status.NO_CONTENT).entity(response).build(); - } - - @PUT - @Path("/servicetoken") - public Response renewServiceJWT(@Context SecurityContext sc) throws JWTException { - serviceJWTKeepAlive.forceRenewServiceToken(); - return Response.noContent().build(); - } @ApiOperation(value = "Get kafka system settings") @GET diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/UsersAdminResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/UsersAdminResource.java index 0073f62a83..437f16c488 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/UsersAdminResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/UsersAdminResource.java @@ -19,7 +19,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.admin.dto.NewUserDTO; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.user.BbcGroupDTO; import io.hops.hopsworks.api.user.UserIds; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/cloud/CloudRoleMappingResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/cloud/CloudRoleMappingResource.java index bc5007bd6c..4bc7414863 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/cloud/CloudRoleMappingResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/cloud/CloudRoleMappingResource.java @@ -15,8 +15,8 @@ */ package io.hops.hopsworks.api.admin.cloud; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; import io.hops.hopsworks.jwt.annotation.JWTRequired; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.swagger.annotations.Api; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/conf/ConfigurationResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/conf/ConfigurationResource.java index d76cbe37f3..49e648f863 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/conf/ConfigurationResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/conf/ConfigurationResource.java @@ -17,7 +17,7 @@ package io.hops.hopsworks.api.admin.conf; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dao.util.VariablesFacade; import io.hops.hopsworks.common.util.Settings; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/projects/ProjectsAdminResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/projects/ProjectsAdminResource.java index facf6de85a..ae3a8ea521 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/projects/ProjectsAdminResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/projects/ProjectsAdminResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.admin.dto.ProjectAdminInfoDTO; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dao.project.ProjectFacade; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/DatasetResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/DatasetResource.java index ea5c44fef7..5aa8d9f827 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/DatasetResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/DatasetResource.java @@ -21,7 +21,7 @@ import io.hops.hopsworks.api.dataset.tags.DatasetTagsResource; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.DownloadService; import io.hops.hopsworks.api.util.Pagination; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/tags/DatasetTagsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/tags/DatasetTagsResource.java index c88ca3c7a9..a6e2597217 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/tags/DatasetTagsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/dataset/tags/DatasetTagsResource.java @@ -21,7 +21,7 @@ import io.hops.hopsworks.api.tags.TagsExpansionBeanParam; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dataset.util.DatasetHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/experiments/ExperimentsBuilder.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/experiments/ExperimentsBuilder.java index 579339e6e9..f77acd5f71 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/experiments/ExperimentsBuilder.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/experiments/ExperimentsBuilder.java @@ -40,9 +40,9 @@ import io.hops.hopsworks.exceptions.DatasetException; import io.hops.hopsworks.exceptions.ExperimentsException; import io.hops.hopsworks.exceptions.GenericException; -import io.hops.hopsworks.exceptions.InvalidQueryException; import io.hops.hopsworks.exceptions.MetadataException; import io.hops.hopsworks.exceptions.ProvenanceException; +import io.hops.hopsworks.persistence.InvalidQueryException; import io.hops.hopsworks.persistence.entity.dataset.Dataset; import io.hops.hopsworks.persistence.entity.hdfs.inode.Inode; import io.hops.hopsworks.persistence.entity.hdfs.user.HdfsUsers; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreKeywordResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreKeywordResource.java index 6487ffee1d..7111dfb6ae 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreKeywordResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreKeywordResource.java @@ -19,7 +19,7 @@ import io.hops.hopsworks.common.featurestore.featureview.FeatureViewController; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.featurestore.featuregroup.FeaturegroupController; import io.hops.hopsworks.common.featurestore.keyword.KeywordDTO; @@ -127,7 +127,7 @@ public Response getKeywords(@Context SecurityContext sc, } return Response.ok().entity(dto).build(); } - + @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreService.java index 9835fb4dc9..1c4cc61ea5 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FeaturestoreService.java @@ -27,7 +27,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.NoCacheResponse; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dao.project.ProjectFacade; import io.hops.hopsworks.common.featurestore.FeaturestoreController; @@ -111,7 +111,7 @@ public class FeaturestoreService { public void setProjectId(Integer projectId) { this.project = projectFacade.find(projectId); } - + /** * Endpoint for getting the list of featurestores for the project * @@ -150,7 +150,7 @@ public Response getFeaturestores( new GenericEntity>(featurestores) {}; return noCacheResponse.getNoCacheResponseBuilder(Response.Status.OK).entity(featurestoresGeneric).build(); } - + /** * Endpoint for getting a featurestore with a particular Id * diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FsQueryConstructorResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FsQueryConstructorResource.java index 0ff596be29..3196667dd6 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FsQueryConstructorResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/FsQueryConstructorResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.featurestore.query.FsQueryDTO; import io.hops.hopsworks.common.featurestore.query.QueryDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/activities/ActivityResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/activities/ActivityResource.java index 9eaffe0768..22c63a8afd 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/activities/ActivityResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/activities/ActivityResource.java @@ -19,7 +19,7 @@ import io.hops.hopsworks.common.featurestore.featureview.FeatureViewController; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/code/CodeResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/code/CodeResource.java index 059c88c7c9..7df12f43f3 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/code/CodeResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/code/CodeResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/commit/CommitResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/commit/CommitResource.java index 3e30e95bb4..a4da001b82 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/commit/CommitResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/commit/CommitResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidation/alert/FeatureGroupAlertResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidation/alert/FeatureGroupAlertResource.java index 2a64beefd2..db39e72e17 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidation/alert/FeatureGroupAlertResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidation/alert/FeatureGroupAlertResource.java @@ -30,7 +30,7 @@ import io.hops.hopsworks.api.alert.AlertDTO; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.project.alert.ProjectAlertsDTO; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.alert.AlertController; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationBuilder.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationBuilder.java index 14203b101c..a8149368e1 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationBuilder.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationBuilder.java @@ -17,7 +17,6 @@ package io.hops.hopsworks.api.featurestore.datavalidationv2.expectations; import io.hops.hopsworks.common.api.ResourceRequest; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationController; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationDTO; import io.hops.hopsworks.exceptions.FeaturestoreException; @@ -25,6 +24,7 @@ import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.Expectation; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ExpectationSuite; import io.hops.hopsworks.persistence.entity.project.Project; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -70,7 +70,7 @@ public ExpectationDTO build(UriInfo uriInfo, Project project, ExpectationDTO dtos = new ExpectationDTO(); uri(dtos, uriInfo, project, featuregroup, expectationSuite); - CollectionInfo expectations = + AbstractFacade.CollectionInfo expectations = expectationController.getExpectationsByExpectationSuite(expectationSuite); ArrayList listOfExpectationDTO = new ArrayList<>(); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationResource.java index e97509d3b2..d991d591f0 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/expectations/ExpectationResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationController; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationBuilder.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationBuilder.java index 6c3037b1dd..ca8fc5ccc0 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationBuilder.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationBuilder.java @@ -17,11 +17,11 @@ package io.hops.hopsworks.api.featurestore.datavalidationv2.greatexpectations; import io.hops.hopsworks.common.api.ResourceRequest; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.common.featurestore.datavalidationv2.suites.ExpectationSuiteController; import io.hops.hopsworks.persistence.entity.featurestore.Featurestore; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.GreatExpectation; import io.hops.hopsworks.persistence.entity.project.Project; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -59,7 +59,8 @@ public GreatExpectationDTO build(UriInfo uriInfo, Project project, Featurestore GreatExpectationDTO dtos = new GreatExpectationDTO(); uri(dtos, uriInfo, project, featurestore); - CollectionInfo greatExpectations = expectationSuiteController.getAllGreatExpectations(); + AbstractFacade.CollectionInfo greatExpectations = expectationSuiteController + .getAllGreatExpectations(); dtos.setItems(greatExpectations.getItems().stream() .map(greatExpectation -> build( uriInfo, project, featurestore, greatExpectation)) diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationResource.java index 180123c745..1518e086e8 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/greatexpectations/GreatExpectationResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.jwt.annotation.JWTRequired; import io.hops.hopsworks.persistence.entity.featurestore.Featurestore; import io.hops.hopsworks.persistence.entity.project.Project; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportBuilder.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportBuilder.java index 7ee3324327..8402682162 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportBuilder.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportBuilder.java @@ -18,7 +18,6 @@ import io.hops.hopsworks.api.featurestore.datavalidationv2.results.ValidationResultBuilder; import io.hops.hopsworks.common.api.ResourceRequest; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.common.featurestore.datavalidationv2.reports.ValidationReportController; import io.hops.hopsworks.common.featurestore.datavalidationv2.reports.ValidationReportDTO; import io.hops.hopsworks.common.featurestore.datavalidationv2.results.ValidationResultDTO; @@ -27,6 +26,7 @@ import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ValidationReport; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ValidationResult; import io.hops.hopsworks.persistence.entity.project.Project; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import org.apache.hadoop.fs.Path; import javax.ejb.EJB; @@ -97,7 +97,7 @@ public ValidationReportDTO build(UriInfo uriInfo, ResourceRequest resourceReques ValidationReportDTO dtos = new ValidationReportDTO(); uri(dtos, uriInfo, project, featuregroup); - CollectionInfo validationReports = + AbstractFacade.CollectionInfo validationReports = validationReportController.getAllValidationReportByFeatureGroup( resourceRequest.getOffset(), resourceRequest.getLimit(), resourceRequest.getSort(), resourceRequest.getFilter(), featuregroup); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportResource.java index f2c9eaca84..dd10cfddc6 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/reports/ValidationReportResource.java @@ -20,7 +20,7 @@ import io.hops.hopsworks.common.featurestore.datavalidationv2.reports.ValidationReportDTO; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultBuilder.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultBuilder.java index 8e858ad449..aac502fc21 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultBuilder.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultBuilder.java @@ -17,7 +17,6 @@ package io.hops.hopsworks.api.featurestore.datavalidationv2.results; import io.hops.hopsworks.common.api.ResourceRequest; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.common.featurestore.datavalidationv2.results.ValidationResultController; import io.hops.hopsworks.common.featurestore.datavalidationv2.results.ValidationResultDTO; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.Featuregroup; @@ -33,6 +32,8 @@ import java.util.TimeZone; import java.util.stream.Collectors; import java.text.SimpleDateFormat; + +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import org.json.JSONObject; import org.json.JSONException; @@ -94,7 +95,7 @@ public ValidationResultDTO buildHistory(UriInfo uriInfo, ResourceRequest resourc ValidationResultDTO dtos = new ValidationResultDTO(); uri(dtos, uriInfo, project, featuregroup); - CollectionInfo validationResults = + AbstractFacade.CollectionInfo validationResults = validationResultController.getAllValidationResultByExpectationId( resourceRequest.getOffset(), resourceRequest.getLimit(), resourceRequest.getSort(), resourceRequest.getFilter(), expectationId); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultResource.java index 6a6207f17e..eeea8f45c2 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/results/ValidationResultResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.featurestore.datavalidationv2.results.ValidationResultDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/suites/ExpectationSuiteResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/suites/ExpectationSuiteResource.java index c6cf9f9a75..c4b58d715e 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/suites/ExpectationSuiteResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/datavalidationv2/suites/ExpectationSuiteResource.java @@ -19,7 +19,7 @@ import io.hops.hopsworks.api.featurestore.datavalidationv2.expectations.ExpectationResource; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jobs.JobDTO; import io.hops.hopsworks.api.jobs.JobsBuilder; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeatureGroupPreviewResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeatureGroupPreviewResource.java index 829f0b65ff..a29e43538c 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeatureGroupPreviewResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeatureGroupPreviewResource.java @@ -19,7 +19,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.featurestore.featuregroup.FeaturegroupController; import io.hops.hopsworks.common.featurestore.featuregroup.cached.FeatureGroupStorage; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeaturegroupService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeaturegroupService.java index 79bacf2da6..af5348ba97 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeaturegroupService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featuregroup/FeaturegroupService.java @@ -35,7 +35,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.NoCacheResponse; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.featurestore.FeaturestoreController; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featureview/FeatureViewResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featureview/FeatureViewResource.java index 37fe6e061f..414eed6b2a 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featureview/FeatureViewResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/featureview/FeatureViewResource.java @@ -19,7 +19,7 @@ import com.google.api.client.util.Sets; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dao.QueryParam; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/preparestatement/PreparedStatementResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/preparestatement/PreparedStatementResource.java index 55fc746ef5..385ede1b95 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/preparestatement/PreparedStatementResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/preparestatement/PreparedStatementResource.java @@ -20,7 +20,7 @@ import io.hops.hopsworks.api.featurestore.trainingdataset.PreparedStatementBuilder; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.featurestore.query.ServingPreparedStatementDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/query/QueryResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/query/QueryResource.java index f285f46a7c..fb509f26ae 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/query/QueryResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/query/QueryResource.java @@ -20,7 +20,7 @@ import io.hops.hopsworks.common.featurestore.featureview.FeatureViewController; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.featurestore.query.FsQueryDTO; import io.hops.hopsworks.common.featurestore.query.Query; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/statistics/StatisticsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/statistics/StatisticsResource.java index 0ef27881cd..22342bc211 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/statistics/StatisticsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/statistics/StatisticsResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jobs.JobDTO; import io.hops.hopsworks.api.jobs.JobsBuilder; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/storageconnector/FeaturestoreStorageConnectorService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/storageconnector/FeaturestoreStorageConnectorService.java index d844dd2876..2a832f837e 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/storageconnector/FeaturestoreStorageConnectorService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/storageconnector/FeaturestoreStorageConnectorService.java @@ -21,7 +21,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.NoCacheResponse; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.featurestore.FeaturestoreController; import io.hops.hopsworks.common.featurestore.FeaturestoreDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/tag/FeatureStoreTagResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/tag/FeatureStoreTagResource.java index 3ab5cb72b9..5b0b9053bc 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/tag/FeatureStoreTagResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/tag/FeatureStoreTagResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.tags.TagsExpansionBeanParam; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.featurestore.metadata.FeatureStoreTagControllerIface; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetResource.java index d548f620dd..7fe010f11d 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetResource.java @@ -21,7 +21,7 @@ import io.hops.hopsworks.api.featurestore.statistics.StatisticsResource; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jobs.JobDTO; import io.hops.hopsworks.api.jobs.JobsBuilder; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetService.java index b52d8ee0b2..9cb64f1196 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/trainingdataset/TrainingDatasetService.java @@ -27,7 +27,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.NoCacheResponse; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jobs.JobDTO; import io.hops.hopsworks.api.jobs.JobsBuilder; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformation/TransformationResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformation/TransformationResource.java index 441d90bae2..545dee6e5d 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformation/TransformationResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformation/TransformationResource.java @@ -20,7 +20,7 @@ import io.hops.hopsworks.api.featurestore.transformationFunction.TransformationFunctionBuilder; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.featurestore.trainingdatasets.TrainingDatasetController; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformationFunction/TransformationFunctionResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformationFunction/TransformationFunctionResource.java index 70fd33ab82..21dc3b0ceb 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformationFunction/TransformationFunctionResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/featurestore/transformationFunction/TransformationFunctionResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/ApiKeyFilter.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/ApiKeyFilter.java new file mode 100644 index 0000000000..379af95107 --- /dev/null +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/ApiKeyFilter.java @@ -0,0 +1,83 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ + +package io.hops.hopsworks.api.filter; + +import io.hops.hopsworks.api.auth.Configuration; +import io.hops.hopsworks.api.auth.UserStatusValidator; +import io.hops.hopsworks.api.auth.UserUtilities; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyUtilities; +import io.hops.hopsworks.exceptions.ApiKeyException; +import io.hops.hopsworks.exceptions.UserException; +import io.hops.hopsworks.persistence.entity.user.Users; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; +import io.hops.hopsworks.restutils.RESTLogLevel; + +import javax.annotation.Priority; +import javax.ejb.EJB; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +@Provider +@ApiKeyRequired +@Priority(Priorities.AUTHENTICATION - 1) +public class ApiKeyFilter extends io.hops.hopsworks.api.auth.key.ApiKeyFilter { + @EJB + private UserStatusValidator userStatusValidator; + @EJB + private ApiKeyUtilities apiKeyUtilities; + @EJB + private UserUtilities userUtilities; + @EJB + private Configuration conf; + @Context + private ResourceInfo resourceInfo; + + protected void validateUserStatus(Users user) throws UserException { + userStatusValidator.checkStatus(user.getStatus()); + } + + protected ApiKey getApiKey(String key) throws ApiKeyException { + return apiKeyUtilities.getApiKey(key); + } + + protected Set getApiScopes(ApiKey key) { + return apiKeyUtilities.getScopes(key); + } + + protected List getUserRoles(Users user) { + return userUtilities.getUserRoles(user); + } + + protected RESTLogLevel getRestLogLevel() { + return conf.getLogLevel(Configuration.AuthConfigurationKeys.HOPSWORKS_REST_LOG_LEVEL); + } + + protected Class getResourceClass() { + return resourceInfo.getResourceClass(); + } + + protected Method getResourceMethod() { + return resourceInfo.getResourceMethod(); + } +} diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java index 48c2917b46..1318f66890 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java @@ -17,10 +17,10 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyFilter; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; -import io.hops.hopsworks.api.filter.util.HopsworksSecurityContext; -import io.hops.hopsworks.api.filter.util.Subject; +import io.hops.hopsworks.api.auth.HopsworksSecurityContext; +import io.hops.hopsworks.api.auth.Subject; +import io.hops.hopsworks.api.auth.key.ApiKeyFilter; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.util.RESTApiJsonResponse; import io.hops.hopsworks.common.util.Settings; import io.hops.hopsworks.jwt.AlgorithmFactory; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/GitResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/GitResource.java index e2da7c656a..24386ea930 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/GitResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/GitResource.java @@ -18,7 +18,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.git.branch.BranchBuilder; import io.hops.hopsworks.api.git.branch.BranchDTO; import io.hops.hopsworks.api.git.execution.ExecutionBeanParam; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/execution/GitExecutionResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/execution/GitExecutionResource.java index 5e85f56df9..f048ecceb1 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/execution/GitExecutionResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/git/execution/GitExecutionResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/FlinkProxyServlet.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/FlinkProxyServlet.java index 09438c21c9..c155f4da47 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/FlinkProxyServlet.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/FlinkProxyServlet.java @@ -16,7 +16,8 @@ package io.hops.hopsworks.api.jobs; import com.google.common.base.Strings; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyFilter; +import io.hops.hopsworks.api.auth.key.ApiKeyFilter; +import io.hops.hopsworks.api.auth.key.ApiKeyUtilities; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.proxy.ProxyServlet; import io.hops.hopsworks.common.dao.hdfsUser.HdfsUsersFacade; @@ -25,7 +26,6 @@ import io.hops.hopsworks.common.dao.project.team.ProjectTeamFacade; import io.hops.hopsworks.common.dao.user.UserFacade; import io.hops.hopsworks.common.jobs.flink.FlinkMasterAddrCache; -import io.hops.hopsworks.common.user.security.apiKey.ApiKeyController; import io.hops.hopsworks.exceptions.ApiKeyException; import io.hops.hopsworks.persistence.entity.hdfs.user.HdfsUsers; import io.hops.hopsworks.persistence.entity.jobs.history.YarnApplicationstate; @@ -62,7 +62,7 @@ public class FlinkProxyServlet extends ProxyServlet { @EJB private UserFacade userFacade; @EJB - private ApiKeyController apiKeyController; + private ApiKeyUtilities apiKeyUtilities; @EJB private JWTHelper jwtHelper; @@ -84,7 +84,7 @@ protected void service(HttpServletRequest servletRequest, HttpServletResponse se if (authorizationHeader.startsWith(ApiKeyFilter.API_KEY)){ try { String key = authorizationHeader.substring(ApiKeyFilter.API_KEY.length()).trim(); - ApiKey apiKey = apiKeyController.getApiKey(key); + ApiKey apiKey = apiKeyUtilities.getApiKey(key); user = apiKey.getUser(); } catch (ApiKeyException e) { servletResponse.sendError(401, "Could not validate API key"); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/JobsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/JobsResource.java index 843406e65d..0fb488cf68 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/JobsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/JobsResource.java @@ -19,7 +19,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jobs.alert.JobAlertsResource; import io.hops.hopsworks.api.jobs.executions.ExecutionsResource; import io.hops.hopsworks.api.jobs.scheduler.JobScheduleV2Resource; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/alert/JobAlertsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/alert/JobAlertsResource.java index 05020ab2ae..a397a3e998 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/alert/JobAlertsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/alert/JobAlertsResource.java @@ -31,7 +31,7 @@ import io.hops.hopsworks.api.alert.AlertDTO; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.project.alert.ProjectAlertsDTO; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.alert.AlertController; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/executions/ExecutionsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/executions/ExecutionsResource.java index b66fec7a88..9adf5f9f3f 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/executions/ExecutionsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/executions/ExecutionsResource.java @@ -19,7 +19,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/scheduler/JobScheduleV2Resource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/scheduler/JobScheduleV2Resource.java index 51f734bde4..5081461a74 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/scheduler/JobScheduleV2Resource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jobs/scheduler/JobScheduleV2Resource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.jobs.scheduler.JobScheduleV2Controller; import io.hops.hopsworks.common.jobs.scheduler.JobScheduleV2DTO; import io.hops.hopsworks.exceptions.JobException; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java index 4e5f2e7597..b46ddd3797 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java @@ -17,15 +17,13 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.google.common.base.Strings; +import io.hops.hopsworks.api.auth.UserUtilities; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.user.ServiceJWTDTO; import io.hops.hopsworks.common.dao.project.ProjectFacade; import io.hops.hopsworks.common.dao.project.team.ProjectTeamFacade; import io.hops.hopsworks.common.dao.user.BbcGroupFacade; import io.hops.hopsworks.common.dao.user.UserFacade; import io.hops.hopsworks.common.opensearch.OpenSearchJWTController; -import io.hops.hopsworks.common.user.UsersController; -import io.hops.hopsworks.common.util.DateUtils; import io.hops.hopsworks.common.util.Settings; import io.hops.hopsworks.exceptions.OpenSearchException; import io.hops.hopsworks.jwt.Constants; @@ -43,7 +41,6 @@ import io.hops.hopsworks.persistence.entity.user.BbcGroup; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.user.security.ua.UserAccountStatus; -import org.apache.commons.lang3.tuple.Pair; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -57,8 +54,6 @@ import javax.ws.rs.core.SecurityContext; import java.io.IOException; import java.security.NoSuchAlgorithmException; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -95,7 +90,7 @@ public class JWTHelper { @EJB private BbcGroupFacade bbcGroupFacade; @EJB - private UsersController userController; + private UserUtilities userUtilities; @EJB private Settings settings; @EJB @@ -250,7 +245,7 @@ public String createOneTimeToken(Users user, String[] roles, String issuer, Stri public String createToken(Users user, String[] audience, String issuer, Date expiresAt, Map claims) throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException { SignatureAlgorithm alg = SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg()); - String[] roles = userController.getUserRoles(user).toArray(new String[0]); + String[] roles = userUtilities.getUserRoles(user).toArray(new String[0]); claims = jwtController.addDefaultClaimsIfMissing(claims, true, settings.getJWTExpLeewaySec(), roles); return jwtController.createToken(settings.getJWTSigningKeyName(), false, issuer, audience, expiresAt, @@ -423,7 +418,7 @@ public String autoRenewToken(DecodedJWT decodedJWT) if (!UserAccountStatus.ACTIVATED_ACCOUNT.equals(user.getStatus())) { throw new NotRenewableException("User not active"); } - List roles = userController.getUserRoles(user); + List roles = userUtilities.getUserRoles(user); return jwtController.autoRenewToken(decodedJWT, roles.toArray(new String[0])); } @@ -450,56 +445,6 @@ public JWTResponseDTO renewToken(JsonWebTokenDTO jsonWebTokenDTO, boolean invali return new JWTResponseDTO(token, newExp, nbf, expLeeway); } - /** - * Helper method to generate one-time tokens for service JWT renewal and renew the - * master service JWT - * @param token2renew Service JWT to renew - * @param oneTimeRenewalToken Valid one-time token associated with the master token to be renewed. - * One time tokens are generated once a service is logged-in and every time - * it renews its master token - * @param user Logged in user - * @param remoteHostname Hostname of the machine the service runs - * @return Renewed master service JWT and five one-time tokens used to renew it - * @throws JWTException - * @throws NoSuchAlgorithmException - */ - public ServiceJWTDTO renewServiceToken(JsonWebTokenDTO token2renew, String oneTimeRenewalToken, - Users user, String remoteHostname) throws JWTException, NoSuchAlgorithmException { - if (Strings.isNullOrEmpty(oneTimeRenewalToken)) { - throw new VerificationException("Service renewal token cannot be null or empty"); - } - if (user == null) { - DecodedJWT decodedJWT = jwtController.decodeToken(oneTimeRenewalToken); - throw new VerificationException("Could not find user associated with JWT with ID: " + decodedJWT.getId()); - } - - LocalDateTime now = DateUtils.getNow(); - Date expiresAt = token2renew.getExpiresAt() != null ? token2renew.getExpiresAt() - : DateUtils.localDateTime2Date(now.plus(settings.getServiceJWTLifetimeMS(), ChronoUnit.MILLIS)); - Date notBefore = token2renew.getNbf() != null ? token2renew.getNbf() - : DateUtils.localDateTime2Date(now); - - List userRoles = userController.getUserRoles(user); - Pair renewedTokens = jwtController.renewServiceToken(oneTimeRenewalToken, token2renew.getToken(), - expiresAt, notBefore, settings.getServiceJWTLifetimeMS(), user.getUsername(), - userRoles, SERVICE_RENEW_JWT_AUDIENCE, remoteHostname, settings.getJWTIssuer(), - settings.getJWTSigningKeyName(), false); - - int expLeeway = jwtController.getExpLeewayClaim(jwtController.decodeToken(renewedTokens.getLeft())); - JWTResponseDTO renewedServiceToken = new JWTResponseDTO(renewedTokens.getLeft(), expiresAt, notBefore, expLeeway); - - return new ServiceJWTDTO(renewedServiceToken, renewedTokens.getRight()); - } - - /** - * Invalidate a service master token and delete the signing key of the temporary - * one-time tokens. - * @param serviceToken2invalidate - */ - public void invalidateServiceToken(String serviceToken2invalidate) { - jwtController.invalidateServiceToken(serviceToken2invalidate, settings.getJWTSigningKeyName()); - } - /** * Invalidate a jwt found in the request header Authorization field. * @@ -638,7 +583,7 @@ public String createTokenForProxy(Users user) throws DuplicateSigningKeyException, SigningKeyNotFoundException, NoSuchAlgorithmException { SignatureAlgorithm alg = SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg()); Date expiresAt = new Date(System.currentTimeMillis() + settings.getJWTLifetimeMs()); - String[] userRoles = userController.getUserRoles(user).toArray(new String[0]); + String[] userRoles = userUtilities.getUserRoles(user).toArray(new String[0]); String[] audience = new String[] {Audience.PROXY}; Map claims = new HashMap<>(); claims.put(ROLES, userRoles); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java index fd5dddd224..6c9eeb07ce 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java @@ -15,21 +15,16 @@ */ package io.hops.hopsworks.api.jwt; -import com.auth0.jwt.JWT; -import com.auth0.jwt.interfaces.DecodedJWT; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.user.ServiceJWTDTO; import io.hops.hopsworks.common.util.Settings; import io.hops.hopsworks.exceptions.OpenSearchException; -import io.hops.hopsworks.exceptions.HopsSecurityException; import io.hops.hopsworks.jwt.annotation.JWTRequired; import io.hops.hopsworks.jwt.exception.DuplicateSigningKeyException; import io.hops.hopsworks.jwt.exception.InvalidationException; -import io.hops.hopsworks.jwt.exception.JWTException; import io.hops.hopsworks.jwt.exception.NotRenewableException; import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException; -import io.hops.hopsworks.persistence.entity.user.Users; -import io.hops.hopsworks.restutils.RESTCodes; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -53,7 +48,6 @@ import javax.ws.rs.core.SecurityContext; import java.security.NoSuchAlgorithmException; import java.util.HashMap; -import java.util.logging.Level; import java.util.logging.Logger; @Path("/jwt") @@ -74,7 +68,10 @@ public class JWTResource { @POST @ApiOperation(value = "Create application token", response = JWTResponseDTO.class) - public Response createToken(JWTRequestDTO jWTRequestDTO, @Context SecurityContext sc) throws NoSuchAlgorithmException, + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"HOPS_ADMIN", "AGENT"}) + public Response createToken(JWTRequestDTO jWTRequestDTO, + @Context HttpServletRequest req, + @Context SecurityContext sc) throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException { JWTResponseDTO jWTResponseDTO = jWTHelper.createToken(jWTRequestDTO, settings.getJWTIssuer()); return Response.ok().entity(jWTResponseDTO).build(); @@ -82,7 +79,10 @@ public Response createToken(JWTRequestDTO jWTRequestDTO, @Context SecurityContex @PUT @ApiOperation(value = "Renew application token", response = JWTResponseDTO.class) - public Response renewToken(JsonWebTokenDTO jsonWebTokenDTO, @Context SecurityContext sc) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"HOPS_ADMIN", "AGENT"}) + public Response renewToken(JsonWebTokenDTO jsonWebTokenDTO, + @Context HttpServletRequest req, + @Context SecurityContext sc) throws SigningKeyNotFoundException, NotRenewableException, InvalidationException { JWTResponseDTO jWTResponseDTO = jWTHelper.renewToken(jsonWebTokenDTO, true, new HashMap<>(3)); return Response.ok().entity(jWTResponseDTO).build(); @@ -91,9 +91,11 @@ public Response renewToken(JsonWebTokenDTO jsonWebTokenDTO, @Context SecurityCon @DELETE @Path("/{token}") @ApiOperation(value = "Invalidate application token") - public Response invalidateToken( - @ApiParam(value = "Token to invalidate", required = true) - @PathParam("token") String token, @Context SecurityContext sc) throws InvalidationException { + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"HOPS_ADMIN", "AGENT"}) + public Response invalidateToken(@ApiParam(value = "Token to invalidate", required = true) + @PathParam("token") String token, + @Context HttpServletRequest req, + @Context SecurityContext sc) throws InvalidationException { jWTHelper.invalidateToken(token); return Response.ok().build(); } @@ -107,55 +109,6 @@ public Response removeSigingKey( jWTHelper.deleteSigningKeyByName(keyName); return Response.ok().build(); } - - @PUT - @Path("/service") - @ApiOperation(value = "Renew a service JWT without invalidating the previous token", response = ServiceJWTDTO.class) - public Response renewServiceToken(JsonWebTokenDTO jwt, @Context HttpServletRequest request) - throws HopsSecurityException { - // This token should be the one-time renewal token - String token = jWTHelper.getAuthToken(request); - Users user = jWTHelper.getUserPrincipal(request); - if (user == null) { - DecodedJWT decodedJWT = JWT.decode(token); - throw new HopsSecurityException(RESTCodes.SecurityErrorCode.NOT_RENEWABLE_TOKEN, Level.FINE, - "User not found associated with that JWT", "Could not find user in the database associated with JWT " - + decodedJWT.getId()); - } - try { - ServiceJWTDTO renewedTokens = jWTHelper.renewServiceToken(jwt, token, user, request.getRemoteHost()); - return Response.ok().entity(renewedTokens).build(); - } catch (JWTException | NoSuchAlgorithmException ex) { - throw new HopsSecurityException(RESTCodes.SecurityErrorCode.NOT_RENEWABLE_TOKEN, Level.WARNING, - "Could not renew service JWT", "Could not renew service JWT for " + request.getRemoteHost()); - } - } - - @DELETE - @Path("/service/{token}") - @ApiOperation(value = "Invalidate a service JWT and also delete the signing key encoded in the token") - public Response invalidateServiceToken( - @ApiParam(value = "Service token to invalidate", required = true) - @PathParam("token") String token, @Context HttpServletRequest request) - throws HopsSecurityException { - DecodedJWT jwt2invalidate = JWT.decode(token); - - Users user = jWTHelper.getUserPrincipal(request); - if (user == null) { - throw new HopsSecurityException(RESTCodes.SecurityErrorCode.INVALIDATION_ERROR, Level.FINE, - "Could not find registered user", "Could not find registered user associated with JWT " - + jwt2invalidate.getId()); - } - if (!user.getUsername().equals(jwt2invalidate.getSubject())) { - throw new HopsSecurityException(RESTCodes.SecurityErrorCode.INVALIDATION_ERROR, Level.FINE, - "Tried to invalidate token with different subject", - "User " + user.getUsername() + " tried to invalidate token with Subject " + jwt2invalidate.getSubject()); - } - - jWTHelper.invalidateServiceToken(token); - - return Response.ok().build(); - } @GET @Path("/elk/key") diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/kafka/KafkaResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/kafka/KafkaResource.java index 4511d8d545..65c47f69b0 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/kafka/KafkaResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/kafka/KafkaResource.java @@ -41,7 +41,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.kafka.topics.TopicsBeanParam; import io.hops.hopsworks.api.kafka.topics.TopicsBuilder; import io.hops.hopsworks.api.util.Pagination; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryResource.java index df6f63b969..5710b7b90a 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlagRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlags; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryTagResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryTagResource.java index 4caea68547..93b63c16ba 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryTagResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/ModelRegistryTagResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlagRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlags; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/models/ModelsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/models/ModelsResource.java index f94aa087f1..733d462f70 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/models/ModelsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/modelregistry/models/ModelsResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlagRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlags; import io.hops.hopsworks.api.jwt.JWTHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/opensearch/OpenSearchService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/opensearch/OpenSearchService.java index f35e72f333..c2b8b85a15 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/opensearch/OpenSearchService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/opensearch/OpenSearchService.java @@ -40,7 +40,7 @@ package io.hops.hopsworks.api.opensearch; import com.google.common.base.Strings; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.opensearch.featurestore.OpenSearchFeaturestoreBuilder; import io.hops.hopsworks.api.opensearch.featurestore.OpenSearchFeaturestoreDTO; import io.hops.hopsworks.api.opensearch.featurestore.OpenSearchFeaturestoreRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/ProjectService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/ProjectService.java index de712e16f0..9dd66bc4ef 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/ProjectService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/ProjectService.java @@ -48,7 +48,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.NoCacheResponse; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.git.GitResource; import io.hops.hopsworks.api.jobs.JobsResource; import io.hops.hopsworks.api.jupyter.JupyterService; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/alert/ProjectAlertsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/alert/ProjectAlertsResource.java index 380cf159a8..dac16096bc 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/alert/ProjectAlertsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/alert/ProjectAlertsResource.java @@ -33,7 +33,7 @@ import io.hops.hopsworks.api.featurestore.datavalidation.alert.FeatureGroupAlertDTO; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jobs.alert.JobAlertsBuilder; import io.hops.hopsworks.api.jobs.alert.JobAlertsDTO; import io.hops.hopsworks.api.util.Pagination; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/jobconfig/DefaultJobConfigurationResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/jobconfig/DefaultJobConfigurationResource.java index cb8f738f39..708fd9350e 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/jobconfig/DefaultJobConfigurationResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/project/jobconfig/DefaultJobConfigurationResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dao.project.ProjectFacade; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProjectProvenanceResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProjectProvenanceResource.java index 57b784ea85..116e90bdd0 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProjectProvenanceResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProjectProvenanceResource.java @@ -18,7 +18,7 @@ import io.hops.hopsworks.api.dataset.DatasetAccessType; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.provenance.ops.ProvLinksBeanParams; import io.hops.hopsworks.api.provenance.ops.ProvLinksBuilder; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProvenanceResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProvenanceResource.java index f40967b59d..e9898b2826 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProvenanceResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/provenance/ProvenanceResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.provenance.explicit.ExplicitProvenanceExpansionBeanParam; import io.hops.hopsworks.api.provenance.explicit.ProvExplicitLinksBuilder; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/conflicts/EnvironmentConflictsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/conflicts/EnvironmentConflictsResource.java index fa420f16c1..388fe81167 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/conflicts/EnvironmentConflictsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/conflicts/EnvironmentConflictsResource.java @@ -18,7 +18,7 @@ import com.logicalclocks.servicediscoverclient.exceptions.ServiceDiscoveryException; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.python.environment.EnvironmentController; import io.hops.hopsworks.exceptions.PythonException; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/EnvironmentResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/EnvironmentResource.java index 1de934492d..713d400bcc 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/EnvironmentResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/EnvironmentResource.java @@ -19,7 +19,7 @@ import com.logicalclocks.servicediscoverclient.exceptions.ServiceDiscoveryException; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.project.util.DsPath; import io.hops.hopsworks.api.project.util.PathValidator; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/EnvironmentCommandsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/EnvironmentCommandsResource.java index 164b32eb34..26b1d47695 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/EnvironmentCommandsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/EnvironmentCommandsResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.python.command.CommandBeanParam; import io.hops.hopsworks.api.python.command.CommandBuilder; import io.hops.hopsworks.api.python.command.CommandDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/custom/EnvironmentCustomCommandsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/custom/EnvironmentCustomCommandsResource.java index e0ac0343c8..db1dcbb79d 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/custom/EnvironmentCustomCommandsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/command/custom/EnvironmentCustomCommandsResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.python.command.CommandBuilder; import io.hops.hopsworks.api.python.command.CommandDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/history/EnvironmentHistoryResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/history/EnvironmentHistoryResource.java index 82b82226f2..7f9007b98e 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/history/EnvironmentHistoryResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/environment/history/EnvironmentHistoryResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/LibraryResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/LibraryResource.java index e5e79fa57d..53c12f8961 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/LibraryResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/LibraryResource.java @@ -18,7 +18,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.python.library.command.LibraryCommandsResource; import io.hops.hopsworks.api.python.library.search.LibrarySearchBuilder; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/command/LibraryCommandsResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/command/LibraryCommandsResource.java index 141c3a8488..27fc1f72a1 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/command/LibraryCommandsResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/python/library/command/LibraryCommandsResource.java @@ -17,7 +17,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.python.command.CommandBeanParam; import io.hops.hopsworks.api.python.command.CommandBuilder; import io.hops.hopsworks.api.python.command.CommandDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/ServingService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/ServingService.java index ab97b2c158..b57f0b0b1b 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/ServingService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/ServingService.java @@ -18,7 +18,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.filter.AllowedProjectRoles; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.NoCacheResponse; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlagRequired; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/inference/InferenceResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/inference/InferenceResource.java index 0619c67fe2..f35f2892f7 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/inference/InferenceResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/serving/inference/InferenceResource.java @@ -19,8 +19,7 @@ import com.google.common.base.Strings; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; -import io.hops.hopsworks.api.jwt.JWTHelper; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlagRequired; import io.hops.hopsworks.api.filter.featureFlags.FeatureFlags; import io.hops.hopsworks.common.dao.project.ProjectFacade; @@ -32,7 +31,6 @@ import io.hops.hopsworks.exceptions.ServingException; import io.hops.hopsworks.jwt.annotation.JWTRequired; import io.hops.hopsworks.persistence.entity.project.Project; -import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -71,13 +69,11 @@ public class InferenceResource { private InferenceController inferenceController; @EJB private ProjectFacade projectFacade; - @EJB - private JWTHelper jWTHelper; - + private Project project; private final static Logger logger = Logger.getLogger(InferenceResource.class.getName()); - + public void setProjectId(Integer projectId) { this.project = projectFacade.find(projectId); } @@ -103,7 +99,6 @@ public Response infer( if (!Strings.isNullOrEmpty(modelVersion)) { version = Integer.valueOf(modelVersion.split("/")[2]); } - Users user = jWTHelper.getUserPrincipal(sc); String authHeader = httpHeaders.getRequestHeader(HttpHeaders.AUTHORIZATION).get(0); String inferenceResult = inferenceController.infer(project, sc.getUserPrincipal().getName(), modelName, version, verb, inferenceRequestJson, authHeader); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/tags/TagSchemasResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/tags/TagSchemasResource.java index 692c0cf824..30cda387f4 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/tags/TagSchemasResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/tags/TagSchemasResource.java @@ -16,7 +16,7 @@ package io.hops.hopsworks.api.tags; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.tags.SchemaDTO; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java index 15db60d95c..09c1b1743c 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java @@ -39,6 +39,8 @@ package io.hops.hopsworks.api.user; import com.google.common.base.Strings; +import io.hops.hopsworks.api.auth.UserStatusValidator; +import io.hops.hopsworks.api.auth.UserUtilities; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.JWTNotRequired; import io.hops.hopsworks.api.filter.NoCacheResponse; @@ -49,7 +51,6 @@ import io.hops.hopsworks.common.dao.user.UserFacade; import io.hops.hopsworks.common.user.AuthController; import io.hops.hopsworks.common.user.QrCode; -import io.hops.hopsworks.common.user.UserStatusValidator; import io.hops.hopsworks.common.user.UsersController; import io.hops.hopsworks.common.util.DateUtils; import io.hops.hopsworks.common.util.Settings; @@ -79,7 +80,6 @@ import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.CookieParam; -import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -129,7 +129,9 @@ public class AuthService { private Settings settings; @EJB private JWTController jwtController; - + @EJB + private UserUtilities userUtilities; + @GET @Path("session") @JWTRequired(acceptedTokens = {Audience.API}, allowedUserRoles = {"HOPS_ADMIN", "HOPS_USER", "HOPS_SERVICE_USER"}) @@ -139,7 +141,7 @@ public Response session(@Context HttpServletRequest req) { json.setData(req.getRemoteUser()); return noCacheResponse.getNoCacheResponseBuilder(Response.Status.OK).entity(json).build(); } - + @GET @Path("jwt/session") @JWTRequired(acceptedTokens = {Audience.API}, allowedUserRoles = {"HOPS_ADMIN", "HOPS_USER", "HOPS_SERVICE_USER"}) @@ -256,7 +258,7 @@ public Response serviceLogin(@FormParam("email") String email, @FormParam("passw LocalDateTime masterExpiration = DateUtils.getNow().plus(settings.getServiceJWTLifetimeMS(), ChronoUnit.MILLIS); LocalDateTime notBefore = jwtController.computeNotBefore4ServiceRenewalTokens(masterExpiration); LocalDateTime expiresAt = notBefore.plus(settings.getServiceJWTLifetimeMS(), ChronoUnit.MILLIS); - List userRoles = userController.getUserRoles(user); + List userRoles = userUtilities.getUserRoles(user); JsonWebToken renewalJWTSpec = new JsonWebToken(); renewalJWTSpec.setSubject(user.getUsername()); @@ -291,19 +293,7 @@ public Response serviceLogin(@FormParam("email") String email, @FormParam("passw throw ex; } } - - @DELETE - @Path("/service") - @Produces(MediaType.APPLICATION_JSON) - @JWTNotRequired - public Response serviceLogout(@Context HttpServletRequest request) throws UserException, InvalidationException { - if (jWTHelper.validToken(request, settings.getJWTIssuer())) { - jwtController.invalidateServiceToken(jWTHelper.getAuthToken(request), settings.getJWTSigningKeyName()); - } - logoutAndInvalidateSession(request, null); - return Response.ok().build(); - } - + @GET @Path("isAdmin") @Produces(MediaType.APPLICATION_JSON) @@ -318,7 +308,7 @@ public Response isAdmin(@Context SecurityContext sc, } return Response.ok(json).build(); } - + @POST @Path("register") @Produces(MediaType.APPLICATION_JSON) @@ -451,7 +441,7 @@ private Response login(Users user, String password, HttpServletRequest req, Http throw new UserException(RESTCodes.UserErrorCode.NO_ROLE_FOUND, Level.FINE, null, RESTCodes.UserErrorCode.NO_ROLE_FOUND.getMessage()); } - + statusValidator.checkStatus(user.getStatus()); try { req.login(user.getEmail(), password); diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/UsersResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/UsersResource.java index 0a9bd5a574..4ada4e1629 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/UsersResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/UsersResource.java @@ -41,7 +41,7 @@ import io.hops.hopsworks.api.activities.UserActivitiesResource; import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.JWTNotRequired; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.user.apiKey.ApiKeyResource; import io.hops.hopsworks.api.util.Pagination; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyBuilder.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyBuilder.java index 5ee2d2495a..09bed19048 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyBuilder.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyBuilder.java @@ -15,9 +15,9 @@ */ package io.hops.hopsworks.api.user.apiKey; +import io.hops.hopsworks.api.auth.key.ApiKeyFacade; import io.hops.hopsworks.common.api.ResourceRequest; import io.hops.hopsworks.common.dao.AbstractFacade; -import io.hops.hopsworks.common.dao.user.security.apiKey.ApiKeyFacade; import io.hops.hopsworks.exceptions.ApiKeyException; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyFilterBy.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyFilterBy.java index 3f58c23050..82dacdaebb 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyFilterBy.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyFilterBy.java @@ -15,8 +15,8 @@ */ package io.hops.hopsworks.api.user.apiKey; +import io.hops.hopsworks.api.auth.key.ApiKeyFacade; import io.hops.hopsworks.common.dao.AbstractFacade; -import io.hops.hopsworks.common.dao.user.security.apiKey.ApiKeyFacade; public class ApiKeyFilterBy implements AbstractFacade.FilterBy { diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyResource.java index 39d9d880fd..95b3329e2d 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyResource.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeyResource.java @@ -15,9 +15,10 @@ */ package io.hops.hopsworks.api.user.apiKey; +import io.hops.hopsworks.api.auth.key.ApiKeyUtilities; import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.api.util.Pagination; import io.hops.hopsworks.common.api.ResourceRequest; @@ -64,9 +65,9 @@ @RequestScoped @TransactionAttribute(TransactionAttributeType.NEVER) public class ApiKeyResource { - + private static final Logger LOGGER = Logger.getLogger(ApiKeyResource.class.getName()); - + @EJB private ApiKeyController apikeyController; @EJB @@ -75,7 +76,9 @@ public class ApiKeyResource { private JWTHelper jwtHelper; @EJB private BbcGroupFacade bbcGroupFacade; - + @EJB + private ApiKeyUtilities apiKeyUtilities; + @GET @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get all api keys.", response = ApiKeyDTO.class) @@ -93,7 +96,7 @@ public Response get(@BeanParam Pagination pagination, ApiKeyDTO dto = apikeyBuilder.buildItems(uriInfo, resourceRequest, user); return Response.ok().entity(dto).build(); } - + @GET @Path("{name}") @Produces(MediaType.APPLICATION_JSON) @@ -108,7 +111,7 @@ public Response getByName(@PathParam("name") String name, ApiKeyDTO dto = apikeyBuilder.build(uriInfo, resourceRequest, user, name); return Response.ok().entity(dto).build(); } - + @GET @Path("key") @Produces(MediaType.APPLICATION_JSON) @@ -119,11 +122,11 @@ public Response getByKey(@QueryParam("key") String key, @Context HttpServletRequest req, @Context SecurityContext sc) throws ApiKeyException { ResourceRequest resourceRequest = new ResourceRequest(ResourceRequest.Name.APIKEY); - ApiKey apikey = apikeyController.getApiKey(key); + ApiKey apikey = apiKeyUtilities.getApiKey(key); ApiKeyDTO dto = apikeyBuilder.build(uriInfo, resourceRequest, apikey); return Response.ok().entity(dto).build(); } - + @PUT @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Update an api key.", response = ApiKeyDTO.class) @@ -153,7 +156,7 @@ public Response update(@QueryParam("name") String name, @QueryParam("action") Ap ApiKeyDTO dto = apikeyBuilder.build(uriInfo, resourceRequest, apikey); return Response.ok().entity(dto).build(); } - + @POST @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Create an api key.", response = ApiKeyDTO.class) @@ -170,7 +173,7 @@ public Response create(@QueryParam("name") String name, @QueryParam("scope") Set dto.setKey(apiKey); return Response.created(dto.getHref()).entity(dto).build(); } - + @DELETE @Path("{name}") @ApiOperation(value = "Delete api key by name.") @@ -183,7 +186,7 @@ public Response deleteByName(@PathParam("name") String name, @Context UriInfo ur apikeyController.deleteKey(user, name); return Response.noContent().build(); } - + @DELETE @ApiOperation(value = "Delete all api keys.") @AllowedProjectRoles({AllowedProjectRoles.DATA_OWNER, AllowedProjectRoles.DATA_SCIENTIST}) @@ -194,7 +197,7 @@ public Response deleteAll(@Context UriInfo uriInfo, @Context SecurityContext sc, apikeyController.deleteAll(user); return Response.noContent().build(); } - + @GET @Path("scopes") @Produces(MediaType.APPLICATION_JSON) @@ -211,7 +214,7 @@ public Response getScopes(@Context SecurityContext sc, }; return Response.ok().entity(scopeEntity).build(); } - + @GET @Path("session") @Produces(MediaType.APPLICATION_JSON) @@ -257,6 +260,11 @@ private Set getScopesForUser(Users user) { if (user.getBbcGroupCollection().size() == 1 && user.getBbcGroupCollection().contains(serviceGroup)) { return ApiScope.getServiceUserScopes(); } + + BbcGroup agentGroup = bbcGroupFacade.findByGroupName("AGENT"); + if (user.getBbcGroupCollection().contains(agentGroup)) { + return ApiScope.getAgentUserScopes(); + } return ApiScope.getUnprivileged(); } } \ No newline at end of file diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeySortBy.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeySortBy.java index d43e5aa194..a8a4e3a04c 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeySortBy.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/apiKey/ApiKeySortBy.java @@ -15,8 +15,8 @@ */ package io.hops.hopsworks.api.user.apiKey; +import io.hops.hopsworks.api.auth.key.ApiKeyFacade; import io.hops.hopsworks.common.dao.AbstractFacade; -import io.hops.hopsworks.common.dao.user.security.apiKey.ApiKeyFacade; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/ClusterUtilisationService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/ClusterUtilisationService.java index 9ecc87a6e6..c3333e90f2 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/ClusterUtilisationService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/ClusterUtilisationService.java @@ -42,7 +42,7 @@ import com.logicalclocks.servicediscoverclient.exceptions.ServiceDiscoveryException; import com.logicalclocks.servicediscoverclient.service.Service; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.dao.host.HostsFacade; import io.hops.hopsworks.common.hosts.ServiceDiscoveryController; import io.hops.hopsworks.common.proxies.client.HttpClient; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java index 115cb2da9e..9934748d08 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java @@ -41,7 +41,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import io.hops.hopsworks.api.filter.JWTNotRequired; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.dataset.util.DatasetHelper; import io.hops.hopsworks.common.dataset.util.DatasetPath; import io.hops.hopsworks.api.filter.AllowedProjectRoles; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/UploadService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/UploadService.java index 45e059990f..4efe8314c6 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/UploadService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/UploadService.java @@ -40,7 +40,7 @@ import io.hops.hopsworks.api.filter.AllowedProjectRoles; import io.hops.hopsworks.api.filter.Audience; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.api.jwt.JWTHelper; import io.hops.hopsworks.common.dao.project.ProjectFacade; import io.hops.hopsworks.common.dataset.DatasetController; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/VariablesService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/VariablesService.java index 0635b37455..1d97b06375 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/VariablesService.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/VariablesService.java @@ -41,7 +41,7 @@ import io.hops.hopsworks.api.filter.Audience; import io.hops.hopsworks.api.filter.JWTNotRequired; import io.hops.hopsworks.api.filter.NoCacheResponse; -import io.hops.hopsworks.api.filter.apiKey.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.common.dao.remote.oauth.OauthClientFacade; import io.hops.hopsworks.common.dataset.FolderNameValidator; import io.hops.hopsworks.common.remote.RemoteUserHelper; diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/rest/application/config/ApplicationConfig.java b/hopsworks-api/src/main/java/io/hops/hopsworks/rest/application/config/ApplicationConfig.java index cc2a896b34..84e5e0a427 100644 --- a/hopsworks-api/src/main/java/io/hops/hopsworks/rest/application/config/ApplicationConfig.java +++ b/hopsworks-api/src/main/java/io/hops/hopsworks/rest/application/config/ApplicationConfig.java @@ -59,7 +59,7 @@ public ApplicationConfig() { register(io.hops.hopsworks.api.exception.mapper.RESTApiThrowableMapper.class); register(io.hops.hopsworks.api.filter.ProjectAuthFilter.class); register(io.hops.hopsworks.api.filter.AuthFilter.class); - register(io.hops.hopsworks.api.filter.apiKey.ApiKeyFilter.class); + register(io.hops.hopsworks.api.filter.ApiKeyFilter.class); register(io.hops.hopsworks.api.filter.JWTAutoRenewFilter.class); register(io.hops.hopsworks.api.filter.featureFlags.FeatureFlagFilter.class); register(io.hops.hopsworks.api.jwt.JWTResource.class); diff --git a/hopsworks-ca/pom.xml b/hopsworks-ca/pom.xml index f3b1d75e15..74be93189f 100644 --- a/hopsworks-ca/pom.xml +++ b/hopsworks-ca/pom.xml @@ -85,6 +85,11 @@ io.hops.hopsworks hopsworks-service-discovery + + io.hops.hopsworks + hopsworks-api-auth + ejb + commons-io diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/ApplicationConfig.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/ApplicationConfig.java index b7909d2db1..e006b0c8ba 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/ApplicationConfig.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/ApplicationConfig.java @@ -40,6 +40,7 @@ package io.hops.hopsworks.ca.api; import io.hops.hopsworks.ca.api.exception.mapper.CAThrowableMapper; +import io.hops.hopsworks.ca.api.filter.CaApiKeyFilter; import io.swagger.annotations.Api; import org.glassfish.jersey.server.ResourceConfig; @@ -61,6 +62,7 @@ public ApplicationConfig() { register(org.glassfish.jersey.media.multipart.MultiPartFeature.class); + register(CaApiKeyFilter.class); // JWT filters register(io.hops.hopsworks.ca.api.filter.AuthFilter.class); register(io.hops.hopsworks.ca.api.filter.JWTAutoRenewFilter.class); diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/AppCertsResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/AppCertsResource.java index 7e96d4fb07..cbe3bb57c5 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/AppCertsResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/AppCertsResource.java @@ -40,6 +40,7 @@ package io.hops.hopsworks.ca.api.certificates; import com.google.common.base.Strings; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.ca.api.filter.Audience; import io.hops.hopsworks.ca.api.filter.NoCacheResponse; import io.hops.hopsworks.ca.controllers.CAException; @@ -47,6 +48,7 @@ import io.hops.hopsworks.ca.controllers.PKI; import io.hops.hopsworks.ca.controllers.PKIUtils; import io.hops.hopsworks.jwt.annotation.JWTRequired; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.hops.hopsworks.restutils.RESTCodes; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -92,6 +94,7 @@ public class AppCertsResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response signCSR(CSRView csrView) throws CAException { if (csrView == null || Strings.isNullOrEmpty(csrView.getCsr())) { throw new IllegalArgumentException("Empty CSR"); @@ -114,6 +117,7 @@ public Response signCSR(CSRView csrView) throws CAException { @ApiOperation(value = "Revoke App certificate") @DELETE @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response revokeCertificate( @ApiParam(value = "Identifier of the Certificate to revoke", required = true) @QueryParam("certId") String certId) throws CAException { diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CertificatesResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CertificatesResource.java index 5485a692b2..c0e5779bc6 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CertificatesResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CertificatesResource.java @@ -39,6 +39,7 @@ package io.hops.hopsworks.ca.api.certificates; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.ca.api.filter.Audience; import io.hops.hopsworks.ca.api.filter.NoCacheResponse; import io.hops.hopsworks.ca.controllers.CAException; @@ -46,6 +47,7 @@ import io.hops.hopsworks.ca.controllers.PKIUtils; import io.hops.hopsworks.jwt.annotation.JWTRequired; import io.hops.hopsworks.persistence.entity.pki.PKICertificate; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -117,6 +119,7 @@ public CRLResource getCrlResource() { @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get x509 certificate in pem format", response = CSRView.class) @JWTRequired(acceptedTokens={Audience.SERVICES, Audience.API}, allowedUserRoles={"HOPS_ADMIN"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"HOPS_ADMIN", "AGENT"}) public Response getCertificate( @ApiParam(value = "X.500 name of the Certificate", required = true) @PathParam("name") String name, diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java index e85d257ee4..35a0b39645 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java @@ -40,6 +40,7 @@ package io.hops.hopsworks.ca.api.certificates; import com.google.common.base.Strings; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.ca.api.filter.Audience; import io.hops.hopsworks.ca.api.filter.NoCacheResponse; import io.hops.hopsworks.ca.controllers.CAException; @@ -48,6 +49,7 @@ import io.hops.hopsworks.ca.controllers.PKI; import io.hops.hopsworks.ca.controllers.PKIUtils; import io.hops.hopsworks.jwt.annotation.JWTRequired; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -97,6 +99,7 @@ public HostCertsResource() { } @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Sing Host CSR with IntermediateHopsCA", response = CSRView.class) @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response signCSR(CSRView csrView) throws CAException { if (csrView == null || csrView.getCsr() == null || csrView.getCsr().isEmpty()) { throw new IllegalArgumentException("Empty CSR"); @@ -124,6 +127,7 @@ public Response signCSR(CSRView csrView) throws CAException { @DELETE @ApiOperation(value = "Revoke Host certificate") @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response revokeCertificate( @ApiParam(value = "Identifier of the Certificate to revoke", required = true) @QueryParam("certId") String certId, @@ -160,6 +164,7 @@ public Response revokeCertificate( @DELETE @ApiOperation(value = "Revoke all Host certificates") @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response revokeCertificateGlob( @ApiParam(value = "Hostname of the node to revoke certificates for", required = true) @QueryParam("hostname") diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/KubeCertsResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/KubeCertsResource.java index 30b2b03490..740429f945 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/KubeCertsResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/KubeCertsResource.java @@ -39,18 +39,23 @@ package io.hops.hopsworks.ca.api.certificates; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; +import com.google.common.base.Strings; import io.hops.hopsworks.ca.api.filter.Audience; import io.hops.hopsworks.ca.api.filter.NoCacheResponse; import io.hops.hopsworks.ca.controllers.CAException; import io.hops.hopsworks.ca.controllers.CAInitializationException; +import io.hops.hopsworks.ca.controllers.CertificateNotFoundException; import io.hops.hopsworks.ca.controllers.PKI; import io.hops.hopsworks.ca.controllers.PKIUtils; import io.hops.hopsworks.jwt.annotation.JWTRequired; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.hops.hopsworks.restutils.RESTCodes; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.apache.commons.lang3.tuple.Pair; +import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.operator.OperatorCreationException; import javax.ejb.EJB; @@ -68,6 +73,8 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import static io.hops.hopsworks.ca.controllers.CertificateType.KUBE; @@ -88,6 +95,7 @@ public class KubeCertsResource{ @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response signCSR(CSRView csrView) throws CAException { if (csrView == null || csrView.getCsr() == null || csrView.getCsr().isEmpty()) { throw new IllegalArgumentException("Empty CSR"); @@ -109,15 +117,32 @@ public Response signCSR(CSRView csrView) throws CAException { @ApiOperation(value = "Revoke KubeCA certificates") @DELETE @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response revokeCertificate( - @ApiParam(value = "Identifier of the Certificate to revoke", required = true) @QueryParam("certId") String certId) - throws CAException { - if (certId == null || certId.isEmpty()) { + @ApiParam(value = "Identifier of the Certificate to revoke", required = true) + @QueryParam("certId") String certId, + @ApiParam(value = "Flag whether certId is a full RFC4514 Distinguished Name string") + @QueryParam("exact") Boolean exact) throws CAException { + if (Strings.isNullOrEmpty(certId)) { throw new IllegalArgumentException("Empty certificate identifier"); } + final List subjectsToRevoke = new ArrayList<>(); try { - pki.revokeCertificate(certId, KUBE); + if (!Boolean.TRUE.equals(exact)) { + X500Name certificateName = pkiUtils.parseCertificateSubjectName(certId, KUBE); + List subjects = pkiUtils.findAllValidSubjectsWithPartialMatch(certificateName.toString()); + subjects.forEach(s -> subjectsToRevoke.add(new X500Name(s))); + } else { + subjectsToRevoke.add(new X500Name(certId)); + } + if (subjectsToRevoke.isEmpty()) { + throw new CertificateNotFoundException("Could not find a VALID certificate with ID: " + certId + " Is " + + "exact X509 Name: " + exact); + } + for (X500Name n : subjectsToRevoke) { + pki.revokeCertificate(n, KUBE); + } return Response.ok().build(); } catch (InvalidNameException | GeneralSecurityException | CAInitializationException ex) { throw pkiUtils.certificateRevocationExceptionConvertToCAException(ex, KUBE); @@ -128,6 +153,7 @@ public Response revokeCertificate( @GET @Produces(MediaType.APPLICATION_JSON) @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response getCACert() throws CAException { try { Pair chainOfTrust = pki.getChainOfTrust(pkiUtils.getResponsibleCA(KUBE)); diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/PKIResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/PKIResource.java index 489bf4536a..d9db5ab02a 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/PKIResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/PKIResource.java @@ -15,10 +15,12 @@ */ package io.hops.hopsworks.ca.api.certificates; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.ca.api.filter.Audience; import io.hops.hopsworks.ca.api.filter.NoCacheResponse; import io.hops.hopsworks.ca.controllers.PKI; import io.hops.hopsworks.jwt.annotation.JWTRequired; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import javax.ejb.EJB; import javax.ejb.TransactionAttribute; @@ -44,6 +46,7 @@ public class PKIResource { @PUT @Path("/configuration") + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response reconfigure() { LOGGER.log(Level.INFO, "Loading PKI configuration"); pki.configure(); diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/ProjectCertsResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/ProjectCertsResource.java index 364017ca5c..6c3fb59975 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/ProjectCertsResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/ProjectCertsResource.java @@ -17,6 +17,7 @@ package io.hops.hopsworks.ca.api.certificates; import com.google.common.base.Strings; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.ca.api.filter.Audience; import io.hops.hopsworks.ca.api.filter.NoCacheResponse; import io.hops.hopsworks.ca.controllers.CAException; @@ -24,6 +25,7 @@ import io.hops.hopsworks.ca.controllers.PKI; import io.hops.hopsworks.ca.controllers.PKIUtils; import io.hops.hopsworks.jwt.annotation.JWTRequired; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -63,7 +65,8 @@ public class ProjectCertsResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) - public Response signCSR(CSRView csrView) throws IOException, CAException { + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) + public Response signCSR(CSRView csrView) throws CAException { if (csrView == null || Strings.isNullOrEmpty(csrView.getCsr())) { throw new IllegalArgumentException("Empty CSR"); } @@ -83,6 +86,7 @@ public Response signCSR(CSRView csrView) throws IOException, CAException { @ApiOperation(value = "Revoke Project certificate") @DELETE @JWTRequired(acceptedTokens={Audience.SERVICES}, allowedUserRoles={"AGENT"}) + @ApiKeyRequired(acceptedScopes = {ApiScope.AUTH}, allowedUserRoles = {"AGENT"}) public Response revokeCertificate( @ApiParam(value = "Identifier of the Certificate to revoke", required = true) @QueryParam("certId") String certId) throws IOException, CAException { diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java index 6b68742857..2b2cbc5b80 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java @@ -18,6 +18,8 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; +import io.hops.hopsworks.api.auth.key.ApiKeyFilter; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; import io.hops.hopsworks.ca.api.exception.mapper.CAJsonResponse; import io.hops.hopsworks.ca.configuration.CAConf; import io.hops.hopsworks.restutils.JsonResponse; @@ -32,23 +34,29 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Priority; import javax.ejb.EJB; import javax.ws.rs.Priorities; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Provider; import static io.hops.hopsworks.ca.configuration.CAConf.CAConfKeys.JWT_ISSUER; +import static io.hops.hopsworks.jwt.Constants.WWW_AUTHENTICATE_VALUE; @Provider @JWTRequired @Priority(Priorities.AUTHENTICATION) public class AuthFilter extends JWTFilter { + private final static Logger LOGGER = Logger.getLogger(AuthFilter.class.getName()); + @EJB private JWTController jwtController; @EJB @@ -73,6 +81,17 @@ public boolean isTokenValid(DecodedJWT jwt) { @Override public boolean preJWTFilter(ContainerRequestContext requestContext) throws IOException { + String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); + if (authorizationHeader != null && authorizationHeader.startsWith(ApiKeyFilter.API_KEY)) { + LOGGER.log(Level.FINEST, "{0} found, leaving JWT interceptor", ApiKeyFilter.API_KEY); + if (getApiKeyAnnotation() == null) { + requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, + WWW_AUTHENTICATE_VALUE).entity(responseEntity(Response.Status.UNAUTHORIZED, + "Authorization method not supported.")) + .build()); + } + return false; + } return true; } @@ -132,4 +151,12 @@ public Object responseEntity(Response.Status status, String msg) { jsonResponse.setErrorMsg(msg); return jsonResponse; } + + private ApiKeyRequired getApiKeyAnnotation() { + Class resourceClass = resourceInfo.getResourceClass(); + Method method = resourceInfo.getResourceMethod(); + ApiKeyRequired methodRolesAnnotation = method.getAnnotation(ApiKeyRequired.class); + ApiKeyRequired classRolesAnnotation = resourceClass.getAnnotation(ApiKeyRequired.class); + return methodRolesAnnotation != null ? methodRolesAnnotation : classRolesAnnotation; + } } diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/CaApiKeyFilter.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/CaApiKeyFilter.java new file mode 100644 index 0000000000..7069bab72e --- /dev/null +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/CaApiKeyFilter.java @@ -0,0 +1,84 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ + +package io.hops.hopsworks.ca.api.filter; + +import io.hops.hopsworks.api.auth.Configuration; +import io.hops.hopsworks.api.auth.UserStatusValidator; +import io.hops.hopsworks.api.auth.UserUtilities; +import io.hops.hopsworks.api.auth.key.ApiKeyFilter; +import io.hops.hopsworks.api.auth.key.ApiKeyRequired; +import io.hops.hopsworks.api.auth.key.ApiKeyUtilities; +import io.hops.hopsworks.exceptions.ApiKeyException; +import io.hops.hopsworks.exceptions.UserException; +import io.hops.hopsworks.persistence.entity.user.Users; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; +import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; +import io.hops.hopsworks.restutils.RESTLogLevel; + +import javax.annotation.Priority; +import javax.ejb.EJB; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +@Provider +@ApiKeyRequired +@Priority(Priorities.AUTHENTICATION - 1) +public class CaApiKeyFilter extends ApiKeyFilter { + @EJB + private UserStatusValidator userStatusValidator; + @EJB + private ApiKeyUtilities apiKeyUtilities; + @EJB + private UserUtilities userUtilities; + @EJB + private Configuration conf; + @Context + private ResourceInfo resourceInfo; + + protected void validateUserStatus(Users user) throws UserException { + userStatusValidator.checkStatus(user.getStatus()); + } + + protected ApiKey getApiKey(String key) throws ApiKeyException { + return apiKeyUtilities.getApiKey(key); + } + + protected Set getApiScopes(ApiKey key) { + return apiKeyUtilities.getScopes(key); + } + + protected List getUserRoles(Users user) { + return userUtilities.getUserRoles(user); + } + + protected RESTLogLevel getRestLogLevel() { + return conf.getLogLevel(Configuration.AuthConfigurationKeys.HOPSWORKS_REST_LOG_LEVEL); + } + + protected Class getResourceClass() { + return resourceInfo.getResourceClass(); + } + + protected Method getResourceMethod() { + return resourceInfo.getResourceMethod(); + } +} diff --git a/hopsworks-common/pom.xml b/hopsworks-common/pom.xml index b26e5dd47f..285504f698 100644 --- a/hopsworks-common/pom.xml +++ b/hopsworks-common/pom.xml @@ -67,6 +67,12 @@ hopsworks-rest-utils + + io.hops.hopsworks + hopsworks-api-auth + ejb + + io.hops.hopsworks hopsworks-jwt diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/AbstractFacade.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/AbstractFacade.java index 0ef24a9f6f..720c5022a9 100755 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/AbstractFacade.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/AbstractFacade.java @@ -39,287 +39,13 @@ package io.hops.hopsworks.common.dao; -import io.hops.hopsworks.exceptions.InvalidQueryException; - -import javax.persistence.EntityManager; -import javax.persistence.Query; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -public abstract class AbstractFacade { - - public static Integer BATCH_SIZE = 100; - - protected final Class entityClass; +/* + * Only for compatibility purposes + */ +@Deprecated +public abstract class AbstractFacade extends io.hops.hopsworks.persistence.entity.util.AbstractFacade { public AbstractFacade(Class entityClass) { - this.entityClass = entityClass; - } - - protected abstract EntityManager getEntityManager(); - - public void save(T entity) { - getEntityManager().persist(entity); - } - - public T update(T entity) { - return getEntityManager().merge(entity); - } - - public void remove(T entity) { - if (entity == null) { - return; - } - getEntityManager().remove(getEntityManager().merge(entity)); - getEntityManager().flush(); - } - - public void removeBatch(List entities) { - for (T item : entities) { - getEntityManager().remove(getEntityManager().merge(item)); - } - - getEntityManager().flush(); - } - - public T find(Object id) { - return getEntityManager().find(entityClass, id); - } - - public List findAll() { - javax.persistence.criteria.CriteriaQuery cq = getEntityManager(). - getCriteriaBuilder().createQuery(); - cq.select(cq.from(entityClass)); - return getEntityManager().createQuery(cq).getResultList(); - } - - public List findRange(int[] range) { - javax.persistence.criteria.CriteriaQuery cq = getEntityManager(). - getCriteriaBuilder().createQuery(); - cq.select(cq.from(entityClass)); - javax.persistence.Query q = getEntityManager().createQuery(cq); - q.setMaxResults(range[1] - range[0]); - q.setFirstResult(range[0]); - return q.getResultList(); - } - - public long count() { - javax.persistence.criteria.CriteriaQuery cq = getEntityManager(). - getCriteriaBuilder().createQuery(); - javax.persistence.criteria.Root rt = cq.from(entityClass); - cq.select(getEntityManager().getCriteriaBuilder().count(rt)).where(); - javax.persistence.Query q = getEntityManager().createQuery(cq); - return (Long) q.getSingleResult(); - } - - public void setOffsetAndLim(Integer offset, Integer limit, Query q) { - if (offset != null && offset > 0) { - q.setFirstResult(offset); - } - if (limit != null && limit > 0) { - q.setMaxResults(limit); - } - } - - public String OrderBy(SortBy sortBy) { - return sortBy.getSql() + " " + sortBy.getParam().getSql(); - } - - public String buildQuery(String query, Set filters, - Set sorts, String more) { - return query + buildFilterString(filters, more) + buildSortString(sorts); - } - - public String buildSortString(Set sortBy) { - if (sortBy == null || sortBy.isEmpty()) { - return ""; - } - sortBy.remove(null); - Iterator sort = sortBy.iterator(); - if (!sort.hasNext()) { - return ""; - } - StringBuilder c = new StringBuilder(" ORDER BY " + OrderBy(sort.next())); - for (;sort.hasNext();) { - c.append(", ").append(OrderBy(sort.next())); - } - return c.toString(); - } - - public String buildFilterString(Set filter, String more) { - String s = more == null || more.isEmpty() ? "" : "WHERE " + more; - if (filter == null || filter.isEmpty()) { - return s; - } - filter.remove(null); - Iterator filterBy = filter.iterator(); - if (!filterBy.hasNext()) { - return s; - } - StringBuilder c = new StringBuilder(" WHERE " + filterBy.next().getSql()); - for (;filterBy.hasNext();) { - c.append(" AND ").append(filterBy.next().getSql()); - } - return c.append(more == null || more.isEmpty()? "": " AND " + more).toString(); - } - - public Date getDate(String field, String value) { - String[] formats = {"yyyy-MM-dd'T'HH:mm:ss.SSSX", "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ssX", - "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:sss", "yyyy-MM-dd"}; - Date date = null; - for (int i = 0; i < formats.length && date == null; i++ ) { - date = getDateByFormat(value, formats[i]); - } - if (date == null) { - throw new InvalidQueryException( - "Filter value for " + field + " needs to set valid format. Expected:yyyy-MM-dd hh:mm:ss.SSSX but found: " + - value); - } - return date; - } - - private Date getDateByFormat(String value, String format) { - SimpleDateFormat sdf = new SimpleDateFormat(format); - try { - return sdf.parse(value); - } catch (ParseException e) { - return null; - } - } - - public Integer getIntValue(FilterBy filterBy) { - return getIntValue(filterBy.getField(), filterBy.getParam()); - } - - public Integer getIntValue(String field, String value) { - Integer val; - try { - val = Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new InvalidQueryException("Filter value for " + field + " needs to set an Integer, but found: " + value); - } - return val; - } - - public Long getLongValue(String field, String value) { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - throw new InvalidQueryException("Filter value for " + field + " needs to set a Long, but found: " + value); - } - } - - public List getIntValues(FilterBy filterBy) { - String[] filterStrs = splitFilterParams(filterBy); - List values = new ArrayList<>(); - String field = filterBy.getField(); - Integer val; - for (String filterStr : filterStrs) { - val = getIntValue(field, filterStr); - values.add(val); - } - return values; - } - - public boolean getBooleanValue(String value) { - return "1".equals(value) || "true".equalsIgnoreCase(value); - } - - public > List getEnumValues(FilterBy filterBy, final Class enumType) { - String[] filterStrs = splitFilterParams(filterBy); - List enumObjects = new ArrayList<>(); - String field = filterBy.getField(); - E enumObject; - for (String filterStr : filterStrs) { - enumObject = getEnumValue(field, filterStr, enumType); - enumObjects.add(enumObject); - } - return enumObjects; - } - - public > E getEnumValue(String field, String filterStr, final Class enumType) { - E enumObject; - try { - enumObject = E.valueOf(enumType, filterStr); - } catch (IllegalArgumentException iae) { - throw new InvalidQueryException("Filter value for " + field + " needs to set valid " + field + ", but found: " - + filterStr, iae); - } - return enumObject; - } - - public String[] splitFilterParams(FilterBy filterBy) { - return filterBy.getParam().split(","); - } - - public interface SortBy { - String getValue(); - OrderBy getParam(); - String getSql(); - } - - public interface FilterBy { - String getValue(); - String getParam(); - String getSql(); - String getField(); - } - - public enum OrderBy { - ASC ("ASC", "ASC"), - DESC ("DESC", "DESC"); - - private final String value; - private final String sql; - - private OrderBy(String value, String sql) { - this.value = value; - this.sql = sql; - } - - public String getValue() { - return value; - } - - public String getSql() { - return sql; - } - - @Override - public String toString() { - return value; - } - - } - - public static class CollectionInfo { - private Long count; - private List items; - - public CollectionInfo(Long count, List items) { - this.count = count; - this.items = items; - } - - public Long getCount() { - return count; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public void setCount(Long count) { - this.count = count; - } + super(entityClass); } } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/hdfs/inode/InodeFacade.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/hdfs/inode/InodeFacade.java index 945ecd5c3d..82624e231b 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/hdfs/inode/InodeFacade.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/hdfs/inode/InodeFacade.java @@ -40,6 +40,7 @@ package io.hops.hopsworks.common.dao.hdfs.inode; import io.hops.hopsworks.common.dao.AbstractFacade; +import io.hops.hopsworks.persistence.InvalidQueryException; import io.hops.hopsworks.persistence.entity.hdfs.user.HdfsUsers; import io.hops.hopsworks.common.dao.hdfsUser.HdfsUsersFacade; import io.hops.hopsworks.persistence.entity.project.Project; @@ -47,7 +48,6 @@ import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.common.hdfs.HdfsUsersController; import io.hops.hopsworks.common.util.HopsUtils; -import io.hops.hopsworks.exceptions.InvalidQueryException; import io.hops.hopsworks.persistence.entity.hdfs.inode.Inode; import io.hops.hopsworks.persistence.entity.hdfs.inode.InodePK; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobhistory/ExecutionFacade.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobhistory/ExecutionFacade.java index 2897180851..fef3db75db 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobhistory/ExecutionFacade.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobhistory/ExecutionFacade.java @@ -40,13 +40,13 @@ package io.hops.hopsworks.common.dao.jobhistory; import io.hops.hopsworks.common.dao.AbstractFacade; +import io.hops.hopsworks.persistence.InvalidQueryException; import io.hops.hopsworks.persistence.entity.jobs.description.Jobs; import io.hops.hopsworks.persistence.entity.project.Project; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.jobs.configuration.JobType; import io.hops.hopsworks.persistence.entity.jobs.configuration.history.JobFinalStatus; import io.hops.hopsworks.persistence.entity.jobs.configuration.history.JobState; -import io.hops.hopsworks.exceptions.InvalidQueryException; import io.hops.hopsworks.persistence.entity.jobs.history.Execution; import org.javatuples.Pair; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobs/description/JobFacade.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobs/description/JobFacade.java index 7de819c476..db96991566 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobs/description/JobFacade.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/jobs/description/JobFacade.java @@ -40,9 +40,9 @@ package io.hops.hopsworks.common.dao.jobs.description; import io.hops.hopsworks.common.dao.AbstractFacade; +import io.hops.hopsworks.persistence.InvalidQueryException; import io.hops.hopsworks.persistence.entity.project.Project; import io.hops.hopsworks.persistence.entity.user.Users; -import io.hops.hopsworks.exceptions.InvalidQueryException; import io.hops.hopsworks.persistence.entity.jobs.configuration.JobConfiguration; import io.hops.hopsworks.persistence.entity.jobs.configuration.ScheduleDTO; import io.hops.hopsworks.persistence.entity.jobs.configuration.history.JobState; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/kagent/HostServicesFacade.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/kagent/HostServicesFacade.java index 3e9f5a7685..2cd74f0f47 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/kagent/HostServicesFacade.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/kagent/HostServicesFacade.java @@ -39,10 +39,10 @@ package io.hops.hopsworks.common.dao.kagent; +import io.hops.hopsworks.persistence.InvalidQueryException; import io.hops.hopsworks.persistence.entity.host.ServiceStatus; import io.hops.hopsworks.persistence.entity.kagent.HostServices; import io.hops.hopsworks.common.dao.AbstractFacade; -import io.hops.hopsworks.exceptions.InvalidQueryException; import java.util.List; import javax.ejb.Stateless; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/UserFacade.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/UserFacade.java index a474edab10..e30de0a04a 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/UserFacade.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/dao/user/UserFacade.java @@ -39,7 +39,7 @@ package io.hops.hopsworks.common.dao.user; import io.hops.hopsworks.common.dao.AbstractFacade; -import io.hops.hopsworks.exceptions.InvalidQueryException; +import io.hops.hopsworks.persistence.InvalidQueryException; import io.hops.hopsworks.persistence.entity.user.BbcGroup; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.persistence.entity.user.security.UserGroup; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/expectations/ExpectationController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/expectations/ExpectationController.java index ee065fc4d4..78b8609628 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/expectations/ExpectationController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/expectations/ExpectationController.java @@ -17,7 +17,6 @@ package io.hops.hopsworks.common.featurestore.datavalidationv2.expectations; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.common.featurestore.activity.FeaturestoreActivityFacade; import io.hops.hopsworks.common.featurestore.featuregroup.FeaturegroupController; import io.hops.hopsworks.exceptions.FeaturestoreException; @@ -25,6 +24,7 @@ import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.Expectation; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ExpectationSuite; import io.hops.hopsworks.persistence.entity.user.Users; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import io.hops.hopsworks.restutils.RESTCodes; import org.json.JSONArray; @@ -141,7 +141,8 @@ public void deleteExpectation(Users user, Integer expectationId, boolean logActi } } - public CollectionInfo getExpectationsByExpectationSuite(ExpectationSuite expectationSuite) { + public AbstractFacade.CollectionInfo getExpectationsByExpectationSuite( + ExpectationSuite expectationSuite) { return expectationFacade.findByExpectationSuite(expectationSuite); } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/reports/ValidationReportController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/reports/ValidationReportController.java index b7e05ac643..fc7b95b093 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/reports/ValidationReportController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/reports/ValidationReportController.java @@ -18,9 +18,6 @@ import io.hops.hopsworks.alerting.api.alert.dto.PostableAlert; import io.hops.hopsworks.common.alert.AlertController; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; -import io.hops.hopsworks.common.dao.AbstractFacade.FilterBy; -import io.hops.hopsworks.common.dao.AbstractFacade.SortBy; import io.hops.hopsworks.common.dataset.DatasetController; import io.hops.hopsworks.common.featurestore.FeaturestoreController; import io.hops.hopsworks.common.featurestore.activity.FeaturestoreActivityFacade; @@ -48,6 +45,7 @@ import io.hops.hopsworks.persistence.entity.project.alert.ProjectServiceAlertStatus; import io.hops.hopsworks.persistence.entity.project.service.ProjectServiceEnum; import io.hops.hopsworks.persistence.entity.user.Users; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import io.hops.hopsworks.restutils.RESTCodes; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; @@ -116,8 +114,9 @@ public ValidationReport getValidationReportById(Integer validationReportId) return validationReport.get(); } - public CollectionInfo getAllValidationReportByFeatureGroup(Integer offset, Integer limit, - Set sorts, Set filters, Featuregroup featuregroup) { + public AbstractFacade.CollectionInfo getAllValidationReportByFeatureGroup( + Integer offset, Integer limit, Set sorts, + Set filters, Featuregroup featuregroup) { return validationReportFacade.findByFeaturegroup(offset, limit, sorts, filters, featuregroup); } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/results/ValidationResultController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/results/ValidationResultController.java index d6fa881300..d946a34718 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/results/ValidationResultController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/results/ValidationResultController.java @@ -16,15 +16,13 @@ package io.hops.hopsworks.common.featurestore.datavalidationv2.results; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; -import io.hops.hopsworks.common.dao.AbstractFacade.FilterBy; -import io.hops.hopsworks.common.dao.AbstractFacade.SortBy; import io.hops.hopsworks.common.featurestore.FeaturestoreFacade; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationFacade; import io.hops.hopsworks.exceptions.FeaturestoreException; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.Expectation; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ValidationReport; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ValidationResult; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import io.hops.hopsworks.restutils.RESTCodes; import org.json.JSONException; import org.json.JSONObject; @@ -63,8 +61,9 @@ public class ValidationResultController { @EJB private FeaturestoreFacade featurestoreFacade; - public CollectionInfo getAllValidationResultByExpectationId (Integer offset, Integer limit, - Set sorts, Set filters, Integer expectationId) { + public AbstractFacade.CollectionInfo getAllValidationResultByExpectationId ( + Integer offset, Integer limit, Set sorts, + Set filters, Integer expectationId) { Optional optExpectation = expectationFacade.findById(expectationId); Expectation expectation; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/suites/ExpectationSuiteController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/suites/ExpectationSuiteController.java index c106ed9f33..49e432f4e1 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/suites/ExpectationSuiteController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/datavalidationv2/suites/ExpectationSuiteController.java @@ -17,7 +17,6 @@ package io.hops.hopsworks.common.featurestore.datavalidationv2.suites; import com.google.common.base.Strings; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.common.featurestore.activity.FeaturestoreActivityFacade; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationController; import io.hops.hopsworks.common.featurestore.datavalidationv2.expectations.ExpectationDTO; @@ -30,6 +29,7 @@ import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.ExpectationSuite; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.datavalidationv2.GreatExpectation; import io.hops.hopsworks.persistence.entity.user.Users; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import io.hops.hopsworks.restutils.RESTCodes; import org.json.JSONException; import org.json.JSONObject; @@ -71,7 +71,7 @@ public class ExpectationSuiteController { ////// Great Expectations ////////////////////////////////////////////////// - public CollectionInfo getAllGreatExpectations() { + public AbstractFacade.CollectionInfo getAllGreatExpectations() { return greatExpectationFacade.findAllGreatExpectation(); } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/featuregroup/cached/FeatureGroupCommitController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/featuregroup/cached/FeatureGroupCommitController.java index 0598ee4e78..3e1142ddd4 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/featuregroup/cached/FeatureGroupCommitController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/featurestore/featuregroup/cached/FeatureGroupCommitController.java @@ -16,12 +16,11 @@ package io.hops.hopsworks.common.featurestore.featuregroup.cached; -import io.hops.hopsworks.common.dao.AbstractFacade; import io.hops.hopsworks.common.featurestore.activity.FeaturestoreActivityFacade; -import io.hops.hopsworks.common.dao.AbstractFacade.CollectionInfo; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.Featuregroup; import io.hops.hopsworks.persistence.entity.featurestore.featuregroup.cached.FeatureGroupCommit; import io.hops.hopsworks.persistence.entity.user.Users; +import io.hops.hopsworks.persistence.entity.util.AbstractFacade; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -88,7 +87,8 @@ public Integer countCommitsInRange(Featuregroup featuregroup, Long startTimestam return featureGroupCommitFacade.countCommitsInRange(featuregroup.getId(), startTimestamp, endTimestamp); } - public CollectionInfo getCommitDetails(Integer featureGroupId, Integer limit, Integer offset, + public AbstractFacade.CollectionInfo getCommitDetails( + Integer featureGroupId, Integer limit, Integer offset, Set sort, Set filters) { diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/git/GitJWTManager.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/git/GitJWTManager.java index 892912481b..5653436ecb 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/git/GitJWTManager.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/git/GitJWTManager.java @@ -15,7 +15,7 @@ */ package io.hops.hopsworks.common.git; -import io.hops.hopsworks.common.user.UsersController; +import io.hops.hopsworks.api.auth.UserUtilities; import io.hops.hopsworks.common.util.DateUtils; import io.hops.hopsworks.common.util.Settings; import io.hops.hopsworks.exceptions.GitOpException; @@ -51,7 +51,7 @@ public class GitJWTManager { @EJB private Settings settings; @EJB - private UsersController usersController; + private UserUtilities userUtilities; private final String TOKEN_FILE_NAME = "token.jwt"; @@ -69,7 +69,7 @@ public void materializeJWT(Users user, String tokenPath) throws GitOpException { private String createTokenForGitContainer(Users user, LocalDateTime expirationDate) throws GitOpException { - String[] userRoles = usersController.getUserRoles(user).toArray(new String[1]); + String[] userRoles = userUtilities.getUserRoles(user).toArray(new String[1]); return createTokenForGitContainer(user.getUsername(), userRoles, expirationDate); } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/jupyter/JupyterJWTManager.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/jupyter/JupyterJWTManager.java index b4c99039a8..93cb8b3fee 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/jupyter/JupyterJWTManager.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/jupyter/JupyterJWTManager.java @@ -18,12 +18,12 @@ import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; +import io.hops.hopsworks.api.auth.UserUtilities; import io.hops.hopsworks.common.dao.jupyter.MaterializedJWTFacade; import io.hops.hopsworks.common.dao.jupyter.JupyterSettingsFacade; import io.hops.hopsworks.common.dao.jupyter.config.JupyterFacade; import io.hops.hopsworks.common.dao.project.ProjectFacade; import io.hops.hopsworks.common.dao.user.UserFacade; -import io.hops.hopsworks.common.user.UsersController; import io.hops.hopsworks.common.util.DateUtils; import io.hops.hopsworks.common.util.PayaraClusterManager; import io.hops.hopsworks.common.util.Settings; @@ -91,7 +91,7 @@ public class JupyterJWTManager { @EJB private JWTController jwtController; @EJB - private UsersController usersController; + private UserUtilities userUtilities; @EJB private JupyterFacade jupyterFacade; @Inject @@ -190,7 +190,7 @@ protected void recover() { // We should create a new one String[] audience = new String[]{"api"}; LocalDateTime expirationDate = LocalDateTime.now().plus(settings.getJWTLifetimeMs(), ChronoUnit.MILLIS); - String[] userRoles = usersController.getUserRoles(user).toArray(new String[1]); + String[] userRoles = userUtilities.getUserRoles(user).toArray(new String[1]); try { Map claims = new HashMap<>(3); claims.put(Constants.RENEWABLE, false); @@ -249,7 +249,7 @@ public void materializeJWT(Users user, Project project, JupyterSettings jupyterS LocalDateTime expirationDate = LocalDateTime.now().plus(settings.getJWTLifetimeMs(), ChronoUnit.MILLIS); JupyterJWT jupyterJWT = new JupyterJWT(project, user, expirationDate, new CidAndPort(cid, port)); try { - String[] roles = usersController.getUserRoles(user).toArray(new String[1]); + String[] roles = userUtilities.getUserRoles(user).toArray(new String[1]); MaterializedJWT materializedJWT = new MaterializedJWT(materialID); materializedJWTFacade.persist(materializedJWT); diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/proxies/client/HttpClient.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/proxies/client/HttpClient.java index 544beab0c3..9ec0b774a9 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/proxies/client/HttpClient.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/proxies/client/HttpClient.java @@ -53,8 +53,7 @@ @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) @ConcurrencyManagement(ConcurrencyManagementType.BEAN) public class HttpClient { - - private static final String AUTH_HEADER_CONTENT = "Bearer %s"; + private static final String API_KEY_AUTH_HEADER = "ApiKey %s"; @EJB private Settings settings; @@ -178,7 +177,7 @@ private PoolingHttpClientConnectionManager createConnectionManager( public void setAuthorizationHeader(HttpRequest httpRequest) { httpRequest.setHeader(HttpHeaders.AUTHORIZATION, - String.format(AUTH_HEADER_CONTENT, settings.getServiceMasterJWT())); + String.format(API_KEY_AUTH_HEADER, settings.getServiceApiKey())); } public ObjectMapper getObjectMapper() { diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/ServiceJWTKeepAlive.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/ServiceJWTKeepAlive.java deleted file mode 100644 index 3516fbd6d9..0000000000 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/ServiceJWTKeepAlive.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * This file is part of Hopsworks - * Copyright (C) 2018, Logical Clocks AB. All rights reserved - * - * Hopsworks is free software: you can redistribute it and/or modify it under the terms of - * the GNU Affero General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License along with this program. - * If not, see . - */ - -package io.hops.hopsworks.common.security; - -import com.auth0.jwt.interfaces.DecodedJWT; -import com.google.common.base.Strings; -import io.hops.hopsworks.common.util.DateUtils; -import io.hops.hopsworks.common.util.PayaraClusterManager; -import io.hops.hopsworks.common.util.Settings; -import io.hops.hopsworks.jwt.JWTController; -import io.hops.hopsworks.jwt.exception.JWTException; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.hadoop.util.BackOff; -import org.apache.hadoop.util.ExponentialBackOff; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.annotation.Resource; -import javax.ejb.DependsOn; -import javax.ejb.EJB; -import javax.ejb.Lock; -import javax.ejb.LockType; -import javax.ejb.Singleton; -import javax.ejb.Startup; -import javax.ejb.Timeout; -import javax.ejb.Timer; -import javax.ejb.TimerConfig; -import javax.ejb.TimerService; -import javax.ejb.TransactionAttribute; -import javax.ejb.TransactionAttributeType; -import java.security.NoSuchAlgorithmException; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -@Singleton -@Startup -@DependsOn("Settings") -@TransactionAttribute(TransactionAttributeType.NEVER) -public class ServiceJWTKeepAlive { - - private final static Logger LOGGER = Logger.getLogger(ServiceJWTKeepAlive.class.getName()); - private final static List SERVICE_RENEW_JWT_AUDIENCE = new ArrayList<>(1); - static { - SERVICE_RENEW_JWT_AUDIENCE.add("services"); - } - - @EJB - private Settings settings; - @EJB - private JWTController jwtController; - @EJB - private PayaraClusterManager payaraClusterManager; - @Resource - private TimerService timerService; - private Timer timer; - - private String hostname; - private BackOff backOff; - - @PostConstruct - @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) - public void init() { - hostname = "hopsworks"; - backOff = new ExponentialBackOff.Builder() - .setInitialIntervalMillis(100L) - .setMaximumIntervalMillis(2000L) - .setMultiplier(2) - // We issue five renewal tokens so we have four retries - .setMaximumRetries(4) - .build(); - - // Do not whirl like a sufi - long interval = Math.min(10000, - Math.max(500L, settings.getServiceJWTLifetimeMS() / 2)); - timer = timerService.createIntervalTimer(5000L, interval, new TimerConfig("Service JWT renewer", false)); - } - - @PreDestroy - private void destroyTimer() { - if (timer != null) { - timer.cancel(); - } - } - - @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) - @Timeout - @Lock(LockType.WRITE) - public void renewServiceToken() { - if (!payaraClusterManager.amIThePrimary()) { - return; - } - try { - doRenew(false); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Error renewing service JWT", ex); - } - } - - @Lock(LockType.WRITE) - public void forceRenewServiceToken() throws JWTException { - try { - doRenew(true); - } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "Could not renew service JWT", ex); - throw new JWTException(ex.getMessage(), ex); - } - } - - private void doRenew(boolean force) - throws JWTException, InterruptedException { - String masterToken = settings.getServiceMasterJWT(); - if (Strings.isNullOrEmpty(masterToken)) { - throw new JWTException("Master token is empty!"); - } - LocalDateTime now = DateUtils.getNow(); - DecodedJWT masterJWT = jwtController.decodeToken(masterToken); - if (force || maybeRenewMasterToken(masterJWT, now)) { - String[] renewalTokens = settings.getServiceRenewJWTs(); - List masterJWTRoles = getJWTRoles(masterJWT); - String user = masterJWT.getSubject(); - - backOff.reset(); - int renewIdx = 0; - while (renewIdx < renewalTokens.length) { - String oneTimeToken = renewalTokens[renewIdx]; - Date notBefore = DateUtils.localDateTime2Date(now); - LocalDateTime expiresAt = now.plus(settings.getServiceJWTLifetimeMS(), ChronoUnit.MILLIS); - try { - Pair renewedTokens = jwtController.renewServiceToken(oneTimeToken, masterToken, - DateUtils.localDateTime2Date(expiresAt), notBefore, settings.getServiceJWTLifetimeMS(), user, - masterJWTRoles, SERVICE_RENEW_JWT_AUDIENCE, hostname, settings.getJWTIssuer(), - settings.getJWTSigningKeyName(), force); - LOGGER.log(Level.FINEST, "New master JWT: " + renewedTokens.getLeft()); - updateTokens(renewedTokens); - LOGGER.log(Level.FINEST, "Invalidating JWT: " + masterToken); - jwtController.invalidateServiceToken(masterToken, settings.getJWTSigningKeyName()); - break; - } catch (JWTException | NoSuchAlgorithmException ex) { - renewIdx++; - Long backoffTimeout = backOff.getBackOffInMillis(); - if (backoffTimeout != -1) { - LOGGER.log(Level.WARNING, "Failed to renew service JWT, retrying in {0} ms. {1}", - new Object[]{backoffTimeout, ex.getMessage()}); - TimeUnit.MILLISECONDS.sleep(backoffTimeout); - } else { - backOff.reset(); - throw new JWTException("Cannot renew service JWT", ex); - } - } - } - LOGGER.log(Level.FINE, "Successfully renewed service JWT"); - } - } - - private void updateTokens(Pair tokens) { - settings.setServiceMasterJWT(tokens.getLeft()); - settings.setServiceRenewJWTs(tokens.getRight()); - } - - private List getJWTRoles(DecodedJWT jwt) { - String[] rolesArray = jwtController.getRolesClaim(jwt); - return Arrays.asList(rolesArray); - } - - private boolean maybeRenewMasterToken(DecodedJWT jwt, LocalDateTime now) { - LocalDateTime expiresAt = DateUtils.date2LocalDateTime(jwt.getExpiresAt()); - return expiresAt.isBefore(now) || expiresAt.isEqual(now); - } -} diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/utils/Secret.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/utils/Secret.java index b40203ab9c..e279ad055a 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/utils/Secret.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/utils/Secret.java @@ -15,128 +15,22 @@ */ package io.hops.hopsworks.common.security.utils; -import org.apache.commons.codec.digest.DigestUtils; +public final class Secret extends io.hops.hopsworks.api.auth.Secret { -public final class Secret { - - private static final String KEY_ID_SEPARATOR = "."; - public static final String KEY_ID_SEPARATOR_REGEX = "\\."; - private final String prefix; - private final String secret; - private final String salt; - private final int prefixMinLength; - private final int secretMinLength; - private final int saltMinLength; - private final boolean prefixed; - public Secret(String prefix, String secret, String salt, int prefixMinLength, int secretMinLength, - int saltMinLength) { - this.prefix = prefix; - this.secret = secret; - this.salt = salt; - this.prefixMinLength = prefixMinLength; - this.secretMinLength = secretMinLength; - this.saltMinLength = saltMinLength; - this.prefixed = true; + int saltMinLength) { + super(prefix, secret, salt, prefixMinLength, secretMinLength, saltMinLength); } - + public Secret(String prefix, String secret, String salt) { - this.prefix = prefix; - this.secret = secret; - this.salt = salt; - this.prefixMinLength = 0; - this.secretMinLength = 0; - this.saltMinLength = 0; - this.prefixed = true; + super(prefix, secret, salt); } - + public Secret(String secret, String salt, int secretMinLength, int saltMinLength) { - this.prefix = ""; - this.secret = secret; - this.salt = salt; - this.prefixMinLength = 0; - this.secretMinLength = secretMinLength; - this.saltMinLength = saltMinLength; - this.prefixed = false; + super(secret, salt, secretMinLength, saltMinLength); } - + public Secret(String secret, String salt) { - this.prefix = ""; - this.secret = secret; - this.salt = salt; - this.prefixMinLength = 0; - this.secretMinLength = 0; - this.saltMinLength = 0; - this.prefixed = false; - } - - public String getPrefix() { - return prefix; - } - - public String getSecret() { - return secret; - } - - public String getSalt() { - return salt; - } - - public String getSecretPlusSalt() { - return this.secret + this.salt; - } - - public int getPrefixMinLength() { - return prefixMinLength; - } - - public int getSecretMinLength() { - return secretMinLength; - } - - public int getSaltMinLength() { - return saltMinLength; - } - - public boolean isPrefixed() { - return prefixed; - } - - public String getPrefixPlusSecret() { - return this.prefix + KEY_ID_SEPARATOR + this.secret; - } - - public String getSha256HexDigest() { - String secPlusSalt = getSecretPlusSalt(); - return DigestUtils.sha256Hex(secPlusSalt); - } - - public String getSha512HexDigest() { - String secPlusSalt = getSecretPlusSalt(); - return DigestUtils.sha512Hex(secPlusSalt); - } - - public String getSha1HexDigest() { - String secPlusSalt = getSecretPlusSalt(); - return DigestUtils.sha1Hex(secPlusSalt); - } - - public boolean validateNotNullOrEmpty() { - if (this.prefixed) { - return this.prefix != null && !this.prefix.isEmpty() && this.secret != null && !this.secret.isEmpty() && - this.salt != null && !this.salt.isEmpty(); - } else { - return this.secret != null && !this.secret.isEmpty() && this.salt != null && !this.salt.isEmpty(); - } - } - - public boolean validateSize() { - boolean notNullOrEmpty = validateNotNullOrEmpty(); - if (this.prefixed) { - return notNullOrEmpty && !(this.prefix.length() < prefixMinLength || this.secret.length() < secretMinLength || - this.salt.length() < saltMinLength); - } else { - return notNullOrEmpty && !(this.secret.length() < secretMinLength || this.salt.length() < saltMinLength); - } + super(secret, salt); } } diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java index 27d0b03f14..711ebce73a 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java @@ -40,6 +40,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import io.hops.hopsworks.api.auth.UserStatusValidator; import io.hops.hopsworks.common.dao.certificates.CertsFacade; import io.hops.hopsworks.persistence.entity.certificates.UserCerts; import io.hops.hopsworks.persistence.entity.project.Project; diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UsersController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UsersController.java index 89780ba25f..6c2647d187 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UsersController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/UsersController.java @@ -41,6 +41,7 @@ import com.google.common.base.Strings; import com.google.zxing.WriterException; +import io.hops.hopsworks.api.auth.UserStatusValidator; import io.hops.hopsworks.common.dao.project.ProjectFacade; import io.hops.hopsworks.common.dao.project.team.ProjectTeamFacade; import io.hops.hopsworks.common.dao.user.UsersDTO; @@ -80,7 +81,6 @@ import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Optional; @@ -595,15 +595,6 @@ public boolean isUserInRole(Users user, String groupName) { return user.getBbcGroupCollection().contains(group); } - public List getUserRoles(Users p) { - Collection groupList = p.getBbcGroupCollection(); - List list = new ArrayList<>(); - for (BbcGroup g : groupList) { - list.add(g.getGroupName()); - } - return list; - } - /** * Delete users. Will fail if the user is an initiator of an audit log. * @param u diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/security/apiKey/ApiKeyController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/security/apiKey/ApiKeyController.java index 336d4798f2..708c3aa20e 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/security/apiKey/ApiKeyController.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/security/apiKey/ApiKeyController.java @@ -15,10 +15,10 @@ */ package io.hops.hopsworks.common.user.security.apiKey; +import io.hops.hopsworks.api.auth.key.ApiKeyFacade; +import io.hops.hopsworks.api.auth.key.ApiKeyScopeFacade; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKey; -import io.hops.hopsworks.common.dao.user.security.apiKey.ApiKeyFacade; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiKeyScope; -import io.hops.hopsworks.common.dao.user.security.apiKey.ApiKeyScopeFacade; import io.hops.hopsworks.persistence.entity.user.security.apiKey.ApiScope; import io.hops.hopsworks.persistence.entity.user.Users; import io.hops.hopsworks.common.dao.user.security.ua.UserAccountsEmailMessages; @@ -131,42 +131,6 @@ private List getKeyScopes(Set scopes, ApiKey apiKey) { return keyScopes; } - /** - * - * @param apiKey - * @return - */ - public Set getScopes(ApiKey apiKey) { - Set scopes = new HashSet<>(); - for (ApiKeyScope scope : apiKey.getApiKeyScopeCollection()) { - scopes.add(scope.getScope()); - } - return scopes; - } - - /** - * - * @param key - * @return - * @throws ApiKeyException - */ - public ApiKey getApiKey(String key) throws ApiKeyException { - String[] parts = key.split(Secret.KEY_ID_SEPARATOR_REGEX); - if (parts.length < 2) { - throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_INVALID, Level.FINE); - } - ApiKey apiKey = apiKeyFacade.findByPrefix(parts[0]); - if (apiKey == null) { - throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_NOT_FOUND_IN_DATABASE, Level.FINE); - } - //___MinLength can be set to 0 b/c no validation is needed if the key was in db - Secret secret = new Secret(parts[0], parts[1], apiKey.getSalt()); - if (!secret.getSha256HexDigest().equals(apiKey.getSecret())) { - throw new ApiKeyException(RESTCodes.ApiKeyErrorCode.KEY_INVALID, Level.FINE); - } - return apiKey; - } - /** * * @param user diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java index 217a4129a7..c07771be93 100644 --- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java +++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java @@ -300,9 +300,9 @@ public class Settings implements Serializable { private static final String VARIABLE_JWT_SIGNING_KEY_NAME = "jwt_signing_key_name"; private static final String VARIABLE_JWT_ISSUER_KEY = "jwt_issuer"; - private static final String VARIABLE_SERVICE_MASTER_JWT = "service_master_jwt"; private static final String VARIABLE_SERVICE_JWT_LIFETIME_MS = "service_jwt_lifetime_ms"; private static final String VARIABLE_SERVICE_JWT_EXP_LEEWAY_SEC = "service_jwt_exp_leeway_sec"; + private static final String VARIABLE_SERVICE_API_KEY = "int_service_api_key"; private static final String VARIABLE_CONNECTION_KEEPALIVE_TIMEOUT = "keepalive_timeout"; @@ -322,7 +322,7 @@ public class Settings implements Serializable { private static final String VARIABLE_FS_JAVA_JOB_UTIL_PATH = "fs_java_job_util"; private static final String VARIABLE_HDFS_FILE_OP_JOB_UTIL = "hdfs_file_op_job_util"; private static final String VARIABLE_HDFS_FILE_OP_JOB_DRIVER_MEM = "hdfs_file_op_job_driver_mem"; - + // Storage connectors private final static String VARIABLE_ENABLE_REDSHIFT_STORAGE_CONNECTORS = "enable_redshift_storage_connectors"; @@ -447,7 +447,7 @@ static KubeType fromString(String str){ private static final String VARIABLE_DOCKER_NAMESPACE = "docker_namespace"; private static final String VARIABLE_MANAGED_DOCKER_REGISTRY = "managed_docker_registry"; - + private String setVar(String varName, String defaultValue) { return setStrVar(varName, defaultValue); } @@ -795,7 +795,7 @@ private void populateCache() { KUBE_TAINTED_NODES = setStrVar(VARIABLE_KUBE_TAINTED_NODES, KUBE_TAINTED_NODES); KUBE_TAINTED_NODES_MONITOR_INTERVAL = setStrVar(VARIABLE_KUBE_TAINTED_NODES_MONITOR_INTERVAL, KUBE_TAINTED_NODES_MONITOR_INTERVAL); - + HOPSWORKS_ENTERPRISE = setBoolVar(VARIABLE_HOPSWORKS_ENTERPRISE, HOPSWORKS_ENTERPRISE); JUPYTER_HOST = setStrVar(VARIABLE_JUPYTER_HOST, JUPYTER_HOST); @@ -809,8 +809,6 @@ private void populateCache() { SERVICE_JWT_LIFETIME_MS = setLongVar(VARIABLE_SERVICE_JWT_LIFETIME_MS, SERVICE_JWT_LIFETIME_MS); SERVICE_JWT_EXP_LEEWAY_SEC = setIntVar(VARIABLE_SERVICE_JWT_EXP_LEEWAY_SEC, SERVICE_JWT_EXP_LEEWAY_SEC); - populateServiceJWTCache(); - CONNECTION_KEEPALIVE_TIMEOUT = setIntVar(VARIABLE_CONNECTION_KEEPALIVE_TIMEOUT, CONNECTION_KEEPALIVE_TIMEOUT); FEATURESTORE_DB_DEFAULT_QUOTA = setLongVar(VARIABLE_FEATURESTORE_DEFAULT_QUOTA, FEATURESTORE_DB_DEFAULT_QUOTA); @@ -914,7 +912,7 @@ private void populateCache() { DOCKER_CGROUP_PARENT = setStrVar(VARIABLE_DOCKER_CGROUP_PARENT, DOCKER_CGROUP_PARENT); PROMETHEUS_PORT = setIntVar(VARIABLE_PROMETHEUS_PORT, PROMETHEUS_PORT); - + SKIP_NAMESPACE_CREATION = setBoolVar(VARIABLE_SKIP_NAMESPACE_CREATION, SKIP_NAMESPACE_CREATION); @@ -931,7 +929,7 @@ private void populateCache() { QUOTAS_MAX_PARALLEL_EXECUTIONS); QUOTAS_MAX_PARALLEL_EXECUTIONS = setLongVar(VARIABLE_QUOTAS_MAX_PARALLEL_EXECUTIONS, QUOTAS_MAX_PARALLEL_EXECUTIONS); - + SQL_MAX_SELECT_IN = setIntVar(VARIABLE_SQL_MAX_SELECT_IN, SQL_MAX_SELECT_IN); ENABLE_JUPYTER_PYTHON_KERNEL_NON_KUBERNETES = setBoolVar(VARIABLE_ENABLE_JUPYTER_PYTHON_KERNEL_NON_KUBERNETES, @@ -950,6 +948,8 @@ private void populateCache() { COMMAND_SEARCH_FS_HISTORY_CLEAN_PERIOD); COMMAND_SEARCH_FS_RETRY_PER_CLEAN_INTERVAL = setIntVar(VARIABLE_COMMAND_SEARCH_FS_RETRY_PER_CLEAN_INTERVAL, COMMAND_SEARCH_FS_RETRY_PER_CLEAN_INTERVAL); + SERVICE_API_KEY = setVar(VARIABLE_SERVICE_API_KEY, SERVICE_API_KEY); + OPENSEARCH_DEFAULT_EMBEDDING_INDEX_NAME = setStrVar( VARIABLE_OPENSEARCH_DEFAULT_EMBEDDING_INDEX, OPENSEARCH_DEFAULT_EMBEDDING_INDEX_NAME); OPENSEARCH_NUM_DEFAULT_EMBEDDING_INDEX = setIntVar( @@ -2332,17 +2332,7 @@ public synchronized Boolean isDelaEnabled() { checkCache(); return DELA_ENABLED; } - - private void populateServiceJWTCache() { - SERVICE_MASTER_JWT = setStrVar(VARIABLE_SERVICE_MASTER_JWT, SERVICE_MASTER_JWT); - RENEW_TOKENS = new String[NUM_OF_SERVICE_RENEW_TOKENS]; - for (int i = 0; i < NUM_OF_SERVICE_RENEW_TOKENS; i++) { - String variableKey = String.format(SERVICE_RENEW_TOKEN_VARIABLE_TEMPLATE, i); - String token = setStrVar(variableKey, ""); - RENEW_TOKENS[i] = token; - } - } - + //************************************************ZOOKEEPER******************************************************** public static final int ZOOKEEPER_SESSION_TIMEOUT_MS = 30 * 1000;//30 seconds //Zookeeper END @@ -2380,7 +2370,7 @@ private void populateServiceJWTCache() { private static final String VARIABLE_OAUTH_LOGOUT_REDIRECT_URI = "oauth_logout_redirect_uri"; private static final String VARIABLE_OAUTH_ACCOUNT_STATUS = "oauth_account_status"; private static final String VARIABLE_OAUTH_GROUP_MAPPING = "oauth_group_mapping"; - + private static final String VARIABLE_REMOTE_AUTH_NEED_CONSENT = "remote_auth_need_consent"; private static final String VARIABLE_DISABLE_PASSWORD_LOGIN = "disable_password_login"; @@ -2464,7 +2454,7 @@ private void populateLDAPCache() { OAUTH_LOGOUT_REDIRECT_URI = setStrVar(VARIABLE_OAUTH_LOGOUT_REDIRECT_URI, OAUTH_LOGOUT_REDIRECT_URI); OAUTH_ACCOUNT_STATUS = setIntVar(VARIABLE_OAUTH_ACCOUNT_STATUS, OAUTH_ACCOUNT_STATUS); OAUTH_GROUP_MAPPING = setStrVar(VARIABLE_OAUTH_GROUP_MAPPING, OAUTH_GROUP_MAPPING); - + REMOTE_AUTH_NEED_CONSENT = setBoolVar(VARIABLE_REMOTE_AUTH_NEED_CONSENT, REMOTE_AUTH_NEED_CONSENT); DISABLE_PASSWORD_LOGIN = setBoolVar(VARIABLE_DISABLE_PASSWORD_LOGIN, DISABLE_PASSWORD_LOGIN); @@ -2473,7 +2463,7 @@ private void populateLDAPCache() { LDAP_GROUP_MAPPING_SYNC_INTERVAL = setLongVar(VARIABLE_LDAP_GROUP_MAPPING_SYNC_INTERVAL, LDAP_GROUP_MAPPING_SYNC_INTERVAL); - + VALIDATE_REMOTE_USER_EMAIL_VERIFIED = setBoolVar(VARIABLE_VALIDATE_REMOTE_USER_EMAIL_VERIFIED, VALIDATE_REMOTE_USER_EMAIL_VERIFIED); @@ -2595,7 +2585,7 @@ public synchronized String getOAuthGroupMapping() { checkCache(); return OAUTH_GROUP_MAPPING; } - + public void updateOAuthGroupMapping(String mapping) { updateVariableInternal(VARIABLE_OAUTH_GROUP_MAPPING, mapping, VariablesVisibility.ADMIN); } @@ -3267,32 +3257,10 @@ public synchronized String getJWTIssuer() { return JWT_ISSUER; } - private String SERVICE_MASTER_JWT = ""; - public synchronized String getServiceMasterJWT() { + private String SERVICE_API_KEY = ""; + public synchronized String getServiceApiKey() { checkCache(); - return SERVICE_MASTER_JWT; - } - - public synchronized void setServiceMasterJWT(String JWT) { - updateVariableInternal(VARIABLE_SERVICE_MASTER_JWT, JWT, VariablesVisibility.ADMIN); - em.flush(); - SERVICE_MASTER_JWT = JWT; - } - - private final int NUM_OF_SERVICE_RENEW_TOKENS = 5; - private final static String SERVICE_RENEW_TOKEN_VARIABLE_TEMPLATE = "service_renew_token_%d"; - private String[] RENEW_TOKENS = new String[0]; - public synchronized String[] getServiceRenewJWTs() { - checkCache(); - return RENEW_TOKENS; - } - - public synchronized void setServiceRenewJWTs(String[] renewTokens) { - for (int i = 0; i < renewTokens.length; i++) { - String variableKey = String.format(SERVICE_RENEW_TOKEN_VARIABLE_TEMPLATE, i); - updateVariableInternal(variableKey, renewTokens[i], VariablesVisibility.ADMIN); - } - RENEW_TOKENS = renewTokens; + return SERVICE_API_KEY; } private int CONNECTION_KEEPALIVE_TIMEOUT = 30; diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java index a40f863715..09e43d5184 100644 --- a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java +++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java @@ -26,13 +26,11 @@ import io.hops.hopsworks.jwt.exception.AccessException; import io.hops.hopsworks.jwt.exception.DuplicateSigningKeyException; import io.hops.hopsworks.jwt.exception.InvalidationException; -import io.hops.hopsworks.jwt.exception.JWTException; import io.hops.hopsworks.jwt.exception.NotRenewableException; import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException; import io.hops.hopsworks.jwt.exception.VerificationException; import io.hops.hopsworks.persistence.entity.jwt.InvalidJwt; import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey; -import org.apache.commons.lang3.tuple.Pair; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -465,69 +463,6 @@ public String renewToken(String token, Date newExp, Date notBefore, boolean inva return renewedToken; } - public Pair renewServiceToken(String oneTimeRenewalToken, String serviceToken, Date newExpiration, - Date newNotBefore, Long serviceJWTLifetimeMS, String username, List userRoles, - List audience, String remoteHostname, String issuer, String defaultJWTSigningKeyName, boolean force) - throws JWTException, NoSuchAlgorithmException { - Map claims = new HashMap<>(4); - claims.put(Constants.RENEWABLE, false); - claims.put(Constants.EXPIRY_LEEWAY, 3600); - claims.put(Constants.ROLES, userRoles.toArray(new String[1])); - String renewalKeyName = getServiceOneTimeJWTSigningKeyname(username, remoteHostname); - LocalDateTime masterExpiration = newExpiration.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); - LocalDateTime notBefore = computeNotBefore4ServiceRenewalTokens(masterExpiration); - LocalDateTime expiresAt = notBefore.plus(serviceJWTLifetimeMS, ChronoUnit.MILLIS); - JsonWebToken jwtSpecs = new JsonWebToken(); - jwtSpecs.setSubject(username); - jwtSpecs.setIssuer(issuer); - jwtSpecs.setAudience(audience); - jwtSpecs.setKeyId(renewalKeyName); - jwtSpecs.setNotBefore(localDateTime2Date(notBefore)); - jwtSpecs.setExpiresAt(localDateTime2Date(expiresAt)); - try { - // Then generate the new one-time tokens - String[] renewalTokens = generateOneTimeTokens4ServiceJWTRenewal(jwtSpecs, claims, defaultJWTSigningKeyName); - - String signingKeyId = getSignKeyID(renewalTokens[0]); - DecodedJWT serviceJWT = decodeToken(serviceToken); - claims.clear(); - claims.put(Constants.RENEWABLE, false); - claims.put(Constants.SERVICE_JWT_RENEWAL_KEY_ID, signingKeyId); - claims.put(Constants.EXPIRY_LEEWAY, getExpLeewayClaim(serviceJWT)); - - // Finally renew the service master token - String renewedServiceToken = renewToken(serviceToken, newExpiration, newNotBefore, false, claims, force); - invalidate(oneTimeRenewalToken); - return Pair.of(renewedServiceToken, renewalTokens); - } catch (JWTException | NoSuchAlgorithmException ex) { - if (renewalKeyName != null) { - deleteSigningKey(renewalKeyName); - } - throw ex; - } - } - - public void invalidateServiceToken(String serviceToken2invalidate, String defaultJWTSigningKeyName) { - DecodedJWT serviceJWT2invalidate = decodeToken(serviceToken2invalidate); - try { - invalidate(serviceToken2invalidate); - } catch (InvalidationException ex) { - LOGGER.log(Level.WARNING, "Could not invalidate service JWT with ID " + serviceJWT2invalidate.getId() - + ". Continuing with deleting signing key"); - } - Claim signingKeyID = serviceJWT2invalidate.getClaim(Constants.SERVICE_JWT_RENEWAL_KEY_ID); - if (signingKeyID != null && !signingKeyID.isNull()) { - // Do not use Claim.asInt, it returns null - JwtSigningKey signingKey = findSigningKeyById(Integer.parseInt(signingKeyID.asString())); - if (signingKey != null && defaultJWTSigningKeyName != null) { - if (!defaultJWTSigningKeyName.equals(signingKey.getName()) - && !ONE_TIME_JWT_SIGNING_KEY_NAME.equals(signingKey.getName())) { - deleteSigningKey(signingKey.getName()); - } - } - } - } - public String getSignKeyID(String token) { DecodedJWT jwt = decodeToken(token); return jwt.getKeyId(); diff --git a/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/exceptions/InvalidQueryException.java b/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/InvalidQueryException.java similarity index 96% rename from hopsworks-rest-utils/src/main/java/io/hops/hopsworks/exceptions/InvalidQueryException.java rename to hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/InvalidQueryException.java index 59082379f4..bd80410453 100644 --- a/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/exceptions/InvalidQueryException.java +++ b/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/InvalidQueryException.java @@ -13,13 +13,12 @@ * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see . */ -package io.hops.hopsworks.exceptions; +package io.hops.hopsworks.persistence; import javax.ejb.ApplicationException; @ApplicationException public class InvalidQueryException extends IllegalArgumentException { - public InvalidQueryException() { } @@ -34,5 +33,4 @@ public InvalidQueryException(String message, Throwable cause) { public InvalidQueryException(Throwable cause) { super(cause); } - } diff --git a/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/user/security/apiKey/ApiScope.java b/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/user/security/apiKey/ApiScope.java index 0b15b5f0a3..a1b7eb18b8 100644 --- a/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/user/security/apiKey/ApiScope.java +++ b/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/user/security/apiKey/ApiScope.java @@ -15,6 +15,8 @@ */ package io.hops.hopsworks.persistence.entity.user.security.apiKey; +import com.google.common.collect.Sets; + import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -35,7 +37,8 @@ public enum ApiScope { MODELREGISTRY(false, true), USER(false, true), GIT(false, false), - PYTHON_LIBRARIES(false, false); + PYTHON_LIBRARIES(false, false), + AUTH(true, false); private final boolean privileged; private final boolean saas;// Hopsworks as a service user scopes @@ -70,4 +73,11 @@ public static Set getServiceUserScopes() { .filter(as -> as.saas) .collect(Collectors.toSet()); } + + private static final Set AGENT_SCOPES = Sets.newHashSet(AUTH); + public static Set getAgentUserScopes() { + Set unprivileged = getUnprivileged(); + unprivileged.addAll(AGENT_SCOPES); + return unprivileged; + } } \ No newline at end of file diff --git a/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/util/AbstractFacade.java b/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/util/AbstractFacade.java new file mode 100644 index 0000000000..b192697206 --- /dev/null +++ b/hopsworks-persistence/src/main/java/io/hops/hopsworks/persistence/entity/util/AbstractFacade.java @@ -0,0 +1,301 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ +package io.hops.hopsworks.persistence.entity.util; + +import io.hops.hopsworks.persistence.InvalidQueryException; + +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public abstract class AbstractFacade { + + public static Integer BATCH_SIZE = 100; + + protected final Class entityClass; + + public AbstractFacade(Class entityClass) { + this.entityClass = entityClass; + } + + protected abstract EntityManager getEntityManager(); + + public void save(T entity) { + getEntityManager().persist(entity); + } + + public T update(T entity) { + return getEntityManager().merge(entity); + } + + public void remove(T entity) { + if (entity == null) { + return; + } + getEntityManager().remove(getEntityManager().merge(entity)); + getEntityManager().flush(); + } + + public void removeBatch(List entities) { + for (T item : entities) { + getEntityManager().remove(getEntityManager().merge(item)); + } + + getEntityManager().flush(); + } + + public T find(Object id) { + return getEntityManager().find(entityClass, id); + } + + public List findAll() { + javax.persistence.criteria.CriteriaQuery cq = getEntityManager(). + getCriteriaBuilder().createQuery(); + cq.select(cq.from(entityClass)); + return getEntityManager().createQuery(cq).getResultList(); + } + + public List findRange(int[] range) { + javax.persistence.criteria.CriteriaQuery cq = getEntityManager(). + getCriteriaBuilder().createQuery(); + cq.select(cq.from(entityClass)); + javax.persistence.Query q = getEntityManager().createQuery(cq); + q.setMaxResults(range[1] - range[0]); + q.setFirstResult(range[0]); + return q.getResultList(); + } + + public long count() { + javax.persistence.criteria.CriteriaQuery cq = getEntityManager(). + getCriteriaBuilder().createQuery(); + javax.persistence.criteria.Root rt = cq.from(entityClass); + cq.select(getEntityManager().getCriteriaBuilder().count(rt)).where(); + javax.persistence.Query q = getEntityManager().createQuery(cq); + return (Long) q.getSingleResult(); + } + + public void setOffsetAndLim(Integer offset, Integer limit, Query q) { + if (offset != null && offset > 0) { + q.setFirstResult(offset); + } + if (limit != null && limit > 0) { + q.setMaxResults(limit); + } + } + + public String OrderBy(SortBy sortBy) { + return sortBy.getSql() + " " + sortBy.getParam().getSql(); + } + + public String buildQuery(String query, Set filters, + Set sorts, String more) { + return query + buildFilterString(filters, more) + buildSortString(sorts); + } + + public String buildSortString(Set sortBy) { + if (sortBy == null || sortBy.isEmpty()) { + return ""; + } + sortBy.remove(null); + Iterator sort = sortBy.iterator(); + if (!sort.hasNext()) { + return ""; + } + StringBuilder c = new StringBuilder(" ORDER BY " + OrderBy(sort.next())); + for (;sort.hasNext();) { + c.append(", ").append(OrderBy(sort.next())); + } + return c.toString(); + } + + public String buildFilterString(Set filter, String more) { + String s = more == null || more.isEmpty() ? "" : "WHERE " + more; + if (filter == null || filter.isEmpty()) { + return s; + } + filter.remove(null); + Iterator filterBy = filter.iterator(); + if (!filterBy.hasNext()) { + return s; + } + StringBuilder c = new StringBuilder(" WHERE " + filterBy.next().getSql()); + for (;filterBy.hasNext();) { + c.append(" AND ").append(filterBy.next().getSql()); + } + return c.append(more == null || more.isEmpty()? "": " AND " + more).toString(); + } + + public Date getDate(String field, String value) { + String[] formats = {"yyyy-MM-dd'T'HH:mm:ss.SSSX", "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ssX", + "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:sss", "yyyy-MM-dd"}; + Date date = null; + for (int i = 0; i < formats.length && date == null; i++ ) { + date = getDateByFormat(value, formats[i]); + } + if (date == null) { + throw new InvalidQueryException( + "Filter value for " + field + " needs to set valid format. Expected:yyyy-MM-dd hh:mm:ss.SSSX but found: " + + value); + } + return date; + } + + private Date getDateByFormat(String value, String format) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + try { + return sdf.parse(value); + } catch (ParseException e) { + return null; + } + } + + public Integer getIntValue(FilterBy filterBy) { + return getIntValue(filterBy.getField(), filterBy.getParam()); + } + + public Integer getIntValue(String field, String value) { + Integer val; + try { + val = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new InvalidQueryException("Filter value for " + field + " needs to set an Integer, but found: " + value); + } + return val; + } + + public Long getLongValue(String field, String value) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new InvalidQueryException("Filter value for " + field + " needs to set a Long, but found: " + value); + } + } + + public List getIntValues(FilterBy filterBy) { + String[] filterStrs = splitFilterParams(filterBy); + List values = new ArrayList<>(); + String field = filterBy.getField(); + Integer val; + for (String filterStr : filterStrs) { + val = getIntValue(field, filterStr); + values.add(val); + } + return values; + } + + public boolean getBooleanValue(String value) { + return "1".equals(value) || "true".equalsIgnoreCase(value); + } + + public > List getEnumValues(FilterBy filterBy, final Class enumType) { + String[] filterStrs = splitFilterParams(filterBy); + List enumObjects = new ArrayList<>(); + String field = filterBy.getField(); + E enumObject; + for (String filterStr : filterStrs) { + enumObject = getEnumValue(field, filterStr, enumType); + enumObjects.add(enumObject); + } + return enumObjects; + } + + public > E getEnumValue(String field, String filterStr, final Class enumType) { + E enumObject; + try { + enumObject = E.valueOf(enumType, filterStr); + } catch (IllegalArgumentException iae) { + throw new InvalidQueryException("Filter value for " + field + " needs to set valid " + field + ", but found: " + + filterStr, iae); + } + return enumObject; + } + + public String[] splitFilterParams(FilterBy filterBy) { + return filterBy.getParam().split(","); + } + + public interface SortBy { + String getValue(); + OrderBy getParam(); + String getSql(); + } + + public interface FilterBy { + String getValue(); + String getParam(); + String getSql(); + String getField(); + } + + public enum OrderBy { + ASC ("ASC", "ASC"), + DESC ("DESC", "DESC"); + + private final String value; + private final String sql; + + private OrderBy(String value, String sql) { + this.value = value; + this.sql = sql; + } + + public String getValue() { + return value; + } + + public String getSql() { + return sql; + } + + @Override + public String toString() { + return value; + } + + } + + public static class CollectionInfo { + private Long count; + private List items; + + public CollectionInfo(Long count, List items) { + this.count = count; + this.items = items; + } + + public Long getCount() { + return count; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void setCount(Long count) { + this.count = count; + } + } +} diff --git a/hopsworks-rest-utils/pom.xml b/hopsworks-rest-utils/pom.xml index 3c37a97de4..52030c79b8 100644 --- a/hopsworks-rest-utils/pom.xml +++ b/hopsworks-rest-utils/pom.xml @@ -52,6 +52,10 @@ io.hops hadoop-client-api + + io.hops.hopsworks + hopsworks-persistence + com.fasterxml.jackson.datatype diff --git a/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/RESTApiJsonResponse.java b/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/RESTApiJsonResponse.java new file mode 100644 index 0000000000..6ab3325e40 --- /dev/null +++ b/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/RESTApiJsonResponse.java @@ -0,0 +1,67 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ +package io.hops.hopsworks.restutils; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@XmlRootElement +public class RESTApiJsonResponse extends JsonResponse { + + private String QRCode; + private List fieldErrors; + private Object data; + private String sessionID; + + public RESTApiJsonResponse() { + } + + + public List getFieldErrors() { + return fieldErrors; + } + + public void setFieldErrors(List fieldErrors) { + this.fieldErrors = fieldErrors; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + @XmlElement + public String getSessionID() { + return sessionID; + } + + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + } + + public String getQRCode() { + return QRCode; + } + + public void setQRCode(String QRCode) { + this.QRCode = QRCode; + } + +} diff --git a/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/ThrowableMapper.java b/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/ThrowableMapper.java index d41c95829a..7d38fca5e6 100644 --- a/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/ThrowableMapper.java +++ b/hopsworks-rest-utils/src/main/java/io/hops/hopsworks/restutils/ThrowableMapper.java @@ -18,10 +18,10 @@ import com.google.common.base.Strings; import io.hops.hopsworks.exceptions.GenericException; import io.hops.hopsworks.exceptions.HopsSecurityException; -import io.hops.hopsworks.exceptions.InvalidQueryException; import io.hops.hopsworks.exceptions.ResourceException; import io.hops.hopsworks.exceptions.ServiceException; import io.hops.hopsworks.exceptions.UserException; +import io.hops.hopsworks.persistence.InvalidQueryException; import org.apache.hadoop.security.AccessControlException; import javax.ejb.AccessLocalException; import javax.ejb.EJBException; diff --git a/pom.xml b/pom.xml index 1ad25e9cbc..ad4b5e24fe 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,7 @@ alerting hopsworks-alert hopsworks-service-discovery + hopsworks-api-auth vector-db @@ -584,6 +585,12 @@ hopsworks-service-discovery ${project.version} + + io.hops.hopsworks + hopsworks-api-auth + ${project.version} + ejb + io.hops.metadata