You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:14 UTC
[15/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring
project structure to better isolate extensions
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java
new file mode 100644
index 0000000..11fc09d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalStateExceptionMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Component
+@Provider
+public class IllegalStateExceptionMapper implements ExceptionMapper<IllegalStateException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(IllegalStateExceptionMapper.class);
+
+ @Override
+ public Response toResponse(IllegalStateException exception) {
+ // log the error
+ logger.info(String.format("%s. Returning %s response.", exception, Response.Status.CONFLICT));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, exception);
+ }
+
+ return Response.status(Response.Status.CONFLICT).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
new file mode 100644
index 0000000..15bb227
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/InvalidAuthenticationExceptionMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps access denied exceptions into a client response.
+ */
+@Component
+@Provider
+public class InvalidAuthenticationExceptionMapper implements ExceptionMapper<InvalidAuthenticationException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(InvalidAuthenticationExceptionMapper.class);
+
+ @Override
+ public Response toResponse(InvalidAuthenticationException exception) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, exception);
+ }
+
+ return Response.status(Response.Status.UNAUTHORIZED).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NiFiRegistryJsonProvider.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NiFiRegistryJsonProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NiFiRegistryJsonProvider.java
new file mode 100644
index 0000000..10a044a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NiFiRegistryJsonProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.nifi.registry.serialization.jackson.ObjectMapperProvider;
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.Provider;
+
+@Component
+@Provider
+@Produces(MediaType.APPLICATION_JSON)
+public class NiFiRegistryJsonProvider extends JacksonJaxbJsonProvider {
+
+ public NiFiRegistryJsonProvider() {
+ super();
+ setMapper(ObjectMapperProvider.getMapper());
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotAllowedExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotAllowedExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotAllowedExceptionMapper.java
new file mode 100644
index 0000000..9237735
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotAllowedExceptionMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.NotAllowedException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps exceptions into client responses.
+ */
+@Component
+@Provider
+public class NotAllowedExceptionMapper implements ExceptionMapper<NotAllowedException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(NotAllowedExceptionMapper.class);
+
+ @Override
+ public Response toResponse(NotAllowedException exception) {
+ logger.info(String.format("%s. Returning %s response.", exception, Status.METHOD_NOT_ALLOWED));
+ logger.debug(StringUtils.EMPTY, exception);
+ return Response.status(Status.METHOD_NOT_ALLOWED).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
new file mode 100644
index 0000000..8abd4c0
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/NotFoundExceptionMapper.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * Maps not found exceptions into client responses.
+ */
+@Component
+@Provider
+public class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(NotFoundExceptionMapper.class);
+
+ @Override
+ public Response toResponse(NotFoundException exception) {
+ // log the error
+ logger.info(String.format("%s. Returning %s response.", exception, Response.Status.NOT_FOUND));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, exception);
+ }
+
+ return Response.status(Response.Status.NOT_FOUND).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java
new file mode 100644
index 0000000..a71452d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ResourceNotFoundExceptionMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.exception.ResourceNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Component
+@Provider
+public class ResourceNotFoundExceptionMapper implements ExceptionMapper<ResourceNotFoundException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(ResourceNotFoundExceptionMapper.class);
+
+ @Override
+ public Response toResponse(ResourceNotFoundException exception) {
+ // log the error
+ logger.info(String.format("%s. Returning %s response.", exception, Response.Status.NOT_FOUND));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, exception);
+ }
+
+ return Response.status(Response.Status.NOT_FOUND).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java
new file mode 100644
index 0000000..8f53141
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/SerializationExceptionMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.serialization.SerializationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Component
+@Provider
+public class SerializationExceptionMapper implements ExceptionMapper<SerializationException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(SerializationExceptionMapper.class);
+
+ @Override
+ public Response toResponse(SerializationException exception) {
+ // log the error
+ logger.info(String.format("%s. Returning %s response.", exception, Response.Status.INTERNAL_SERVER_ERROR));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, exception);
+ }
+
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ThrowableMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ThrowableMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ThrowableMapper.java
new file mode 100644
index 0000000..4b434ed
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/ThrowableMapper.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * Maps unknown node exceptions into client responses.
+ */
+@Component
+@Provider
+public class ThrowableMapper implements ExceptionMapper<Throwable> {
+
+ private static final Logger logger = LoggerFactory.getLogger(ThrowableMapper.class);
+
+ @Override
+ public Response toResponse(Throwable exception) {
+ // log the error
+ logger.error(String.format("An unexpected error has occurred: %s. Returning %s response.", exception, Response.Status.INTERNAL_SERVER_ERROR), exception);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An unexpected error has occurred. Please check the logs for additional details.").type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UnauthorizedExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UnauthorizedExceptionMapper.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UnauthorizedExceptionMapper.java
new file mode 100644
index 0000000..1c67e94
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UnauthorizedExceptionMapper.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.web.exception.UnauthorizedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps Unauthorized exceptions into client responses that set the WWW-Authenticate header
+ * with a list of challenges (i.e., acceptable auth scheme types).
+ */
+@Component
+@Provider
+public class UnauthorizedExceptionMapper implements ExceptionMapper<UnauthorizedException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(UnauthorizedExceptionMapper.class);
+
+ private static final String AUTHENTICATION_CHALLENGE_HEADER_NAME = "WWW-Authenticate";
+
+ @Override
+ public Response toResponse(UnauthorizedException exception) {
+
+ logger.info("{}. Returning {} response.", exception, Response.Status.UNAUTHORIZED);
+ logger.debug(StringUtils.EMPTY, exception);
+
+ final Response.ResponseBuilder response = Response.status(Response.Status.UNAUTHORIZED);
+ if (exception.getWwwAuthenticateChallenge() != null) {
+ response.header(AUTHENTICATION_CHALLENGE_HEADER_NAME, exception.getWwwAuthenticateChallenge());
+ }
+ response.entity(exception.getMessage()).type("text/plain");
+ return response.build();
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistryMasterKeyProviderFactory.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistryMasterKeyProviderFactory.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistryMasterKeyProviderFactory.java
new file mode 100644
index 0000000..8026cca
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistryMasterKeyProviderFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security;
+
+import org.apache.nifi.registry.NiFiRegistryApiApplication;
+import org.apache.nifi.registry.security.crypto.CryptoKeyProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.context.ServletContextAware;
+
+import javax.servlet.ServletContext;
+
+@Configuration
+public class NiFiRegistryMasterKeyProviderFactory implements ServletContextAware {
+
+ private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryMasterKeyProviderFactory.class);
+
+ private CryptoKeyProvider masterKeyProvider = null;
+
+ @Bean
+ public CryptoKeyProvider getNiFiRegistryMasterKeyProvider() {
+ return masterKeyProvider;
+ }
+
+ @Override
+ public void setServletContext(ServletContext servletContext) {
+ Object rawKeyProviderObject = servletContext.getAttribute(NiFiRegistryApiApplication.NIFI_REGISTRY_MASTER_KEY_ATTRIBUTE);
+
+ if (rawKeyProviderObject == null) {
+ logger.warn("Value of {} was null. " +
+ "{} bean will not be available in Application Context, so any attempt to load protected property values may fail.",
+ NiFiRegistryApiApplication.NIFI_REGISTRY_MASTER_KEY_ATTRIBUTE,
+ CryptoKeyProvider.class.getSimpleName());
+ return;
+ }
+
+ if (!(rawKeyProviderObject instanceof CryptoKeyProvider)) {
+ logger.warn("Expected value of {} to be of type {}, but instead got {}. " +
+ "{} bean will NOT be available in Application Context, so any attempt to load protected property values may fail.",
+ NiFiRegistryApiApplication.NIFI_REGISTRY_MASTER_KEY_ATTRIBUTE,
+ CryptoKeyProvider.class.getName(),
+ rawKeyProviderObject.getClass().getName(),
+ CryptoKeyProvider.class.getSimpleName());
+ return;
+ }
+
+ logger.info("Updating Application Context with {} bean for obtaining NiFi Registry master key.", CryptoKeyProvider.class.getSimpleName());
+ masterKeyProvider = (CryptoKeyProvider) rawKeyProviderObject;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
new file mode 100644
index 0000000..a2a6ea9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security;
+
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authorization.resource.ResourceType;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.apache.nifi.registry.web.security.authentication.AnonymousIdentityFilter;
+import org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider;
+import org.apache.nifi.registry.web.security.authentication.IdentityFilter;
+import org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException;
+import org.apache.nifi.registry.web.security.authentication.jwt.JwtIdentityProvider;
+import org.apache.nifi.registry.web.security.authentication.x509.X509IdentityAuthenticationProvider;
+import org.apache.nifi.registry.web.security.authentication.x509.X509IdentityProvider;
+import org.apache.nifi.registry.web.security.authorization.ResourceAuthorizationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * NiFi Registry Web Api Spring security
+ */
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private static final Logger logger = LoggerFactory.getLogger(NiFiRegistrySecurityConfig.class);
+
+ @Autowired
+ private NiFiRegistryProperties properties;
+
+ @Autowired
+ private AuthorizationService authorizationService;
+
+ private AnonymousIdentityFilter anonymousAuthenticationFilter = new AnonymousIdentityFilter();
+
+ @Autowired
+ private X509IdentityProvider x509IdentityProvider;
+ private IdentityFilter x509AuthenticationFilter;
+ private IdentityAuthenticationProvider x509AuthenticationProvider;
+
+ @Autowired
+ private JwtIdentityProvider jwtIdentityProvider;
+ private IdentityFilter jwtAuthenticationFilter;
+ private IdentityAuthenticationProvider jwtAuthenticationProvider;
+
+ private ResourceAuthorizationFilter resourceAuthorizationFilter;
+
+ public NiFiRegistrySecurityConfig() {
+ super(true); // disable defaults
+ }
+
+ @Override
+ public void configure(WebSecurity webSecurity) throws Exception {
+ // allow any client to access the endpoint for logging in to generate an access token
+ webSecurity.ignoring().antMatchers( "/access/token/**");
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .rememberMe().disable()
+ .authorizeRequests()
+ .anyRequest().fullyAuthenticated()
+ .and()
+ .exceptionHandling()
+ .authenticationEntryPoint(http401AuthenticationEntryPoint())
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+
+ // x509
+ http.addFilterBefore(x509AuthenticationFilter(), AnonymousAuthenticationFilter.class);
+
+ // jwt
+ http.addFilterBefore(jwtAuthenticationFilter(), AnonymousAuthenticationFilter.class);
+
+ // otp
+ // todo, if needed one-time password auth filter goes here
+
+ if (properties.getSslPort() == null) {
+ // If we are running an unsecured NiFi Registry server, add an
+ // anonymous authentication filter that will populate the
+ // authenticated, anonymous user if no other user identity
+ // is detected earlier in the Spring filter chain.
+ http.anonymous().authenticationFilter(anonymousAuthenticationFilter);
+ }
+
+ // After Spring Security filter chain is complete (so authentication is done),
+ // but before the Jersey application endpoints get the request,
+ // insert the ResourceAuthorizationFilter to do its authorization checks
+ http.addFilterAfter(resourceAuthorizationFilter(), FilterSecurityInterceptor.class);
+
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth
+ .authenticationProvider(x509AuthenticationProvider())
+ .authenticationProvider(jwtAuthenticationProvider());
+ }
+
+ private IdentityFilter x509AuthenticationFilter() throws Exception {
+ if (x509AuthenticationFilter == null) {
+ x509AuthenticationFilter = new IdentityFilter(x509IdentityProvider);
+ }
+ return x509AuthenticationFilter;
+ }
+
+ private IdentityAuthenticationProvider x509AuthenticationProvider() {
+ if (x509AuthenticationProvider == null) {
+ x509AuthenticationProvider = new X509IdentityAuthenticationProvider(properties, authorizationService.getAuthorizer(), x509IdentityProvider);
+ }
+ return x509AuthenticationProvider;
+ }
+
+ private IdentityFilter jwtAuthenticationFilter() throws Exception {
+ if (jwtAuthenticationFilter == null) {
+ jwtAuthenticationFilter = new IdentityFilter(jwtIdentityProvider);
+ }
+ return jwtAuthenticationFilter;
+ }
+
+ private IdentityAuthenticationProvider jwtAuthenticationProvider() {
+ if (jwtAuthenticationProvider == null) {
+ jwtAuthenticationProvider = new IdentityAuthenticationProvider(properties, authorizationService.getAuthorizer(), jwtIdentityProvider);
+ }
+ return jwtAuthenticationProvider;
+ }
+
+ private ResourceAuthorizationFilter resourceAuthorizationFilter() {
+ if (resourceAuthorizationFilter == null) {
+ resourceAuthorizationFilter = ResourceAuthorizationFilter.builder()
+ .setAuthorizationService(authorizationService)
+ .addResourceType(ResourceType.Actuator)
+ .addResourceType(ResourceType.Swagger)
+ .build();
+ }
+ return resourceAuthorizationFilter;
+ }
+
+ private AuthenticationEntryPoint http401AuthenticationEntryPoint() {
+ // This gets used for both secured and unsecured configurations. It will be called by Spring Security if a request makes it through the filter chain without being authenticated.
+ // For unsecured, this should never be reached because the custom AnonymousAuthenticationFilter should always populate a fully-authenticated anonymous user
+ // For secured, this will cause attempt to access any API endpoint (except those explicitly ignored) without providing credentials to return a 401 Unauthorized challenge
+ return new AuthenticationEntryPoint() {
+ @Override
+ public void commence(HttpServletRequest request,
+ HttpServletResponse response,
+ AuthenticationException authenticationException)
+ throws IOException, ServletException {
+
+ final int status;
+
+ // See X509IdentityAuthenticationProvider.buildAuthenticatedToken(...)
+ if (authenticationException instanceof UntrustedProxyException) {
+ // return a 403 response
+ status = HttpServletResponse.SC_FORBIDDEN;
+ logger.info("Identity in proxy chain not trusted to act as a proxy: {} Returning 403 response.", authenticationException.toString());
+
+ } else {
+ // return a 401 response
+ status = HttpServletResponse.SC_UNAUTHORIZED;
+ logger.info("Client could not be authenticated due to: {} Returning 401 response.", authenticationException.toString());
+ }
+
+ logger.debug("", authenticationException);
+
+ if (!response.isCommitted()) {
+ response.setStatus(status);
+ response.setContentType("text/plain");
+ response.getWriter().println(String.format("%s Contact the system administrator.", authenticationException.getLocalizedMessage()));
+ }
+ }
+ };
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/PermissionsService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/PermissionsService.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/PermissionsService.java
new file mode 100644
index 0000000..1e00ee1
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/PermissionsService.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security;
+
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.authorization.Permissions;
+import org.apache.nifi.registry.security.authorization.AuthorizableLookup;
+import org.apache.nifi.registry.security.authorization.resource.Authorizable;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * This is a class that Resource classes can utilized to populate fields
+ * on model objects returned by the {@link org.apache.nifi.registry.service.RegistryService}
+ * before returning them to a client.
+ *
+ * The fields cannot be populated by the RegistryService because they require
+ * the {@link AuthorizationService}, which RegistryService does not depend on.
+ */
+@Service
+public class PermissionsService {
+
+ private AuthorizationService authorizationService;
+ private AuthorizableLookup authorizableLookup;
+
+ @Autowired
+ public PermissionsService(AuthorizationService authorizationService, AuthorizableLookup authorizableLookup) {
+ this.authorizationService = authorizationService;
+ this.authorizableLookup = authorizableLookup;
+ }
+
+ public void populateBucketPermissions(final Iterable<Bucket> buckets) {
+ Permissions topLevelBucketPermissions = authorizationService.getPermissionsForResource(authorizableLookup.getBucketsAuthorizable());
+ buckets.forEach(b -> populateBucketPermissions(b, topLevelBucketPermissions));
+ }
+
+ public void populateBucketPermissions(final Bucket bucket) {
+ populateBucketPermissions(bucket, null);
+ }
+
+ public void populateItemPermissions(final Iterable<? extends BucketItem> bucketItems) {
+ Permissions topLevelBucketPermissions = authorizationService.getPermissionsForResource(authorizableLookup.getBucketsAuthorizable());
+ bucketItems.forEach(i -> populateItemPermissions(i, topLevelBucketPermissions));
+ }
+
+ public void populateItemPermissions(final BucketItem bucketItem) {
+ populateItemPermissions(bucketItem, null);
+ }
+
+ private void populateBucketPermissions(final Bucket bucket, final Permissions knownPermissions) {
+
+ if (bucket == null) {
+ return;
+ }
+
+ Permissions bucketPermissions = createPermissionsForBucketId(bucket.getIdentifier(), knownPermissions);
+ bucket.setPermissions(bucketPermissions);
+
+ }
+
+ private void populateItemPermissions(final BucketItem bucketItem, final Permissions knownPermissions) {
+
+ if (bucketItem == null) {
+ return;
+ }
+
+ Permissions bucketItemPermissions = createPermissionsForBucketId(bucketItem.getBucketIdentifier(), knownPermissions);
+ bucketItem.setPermissions(bucketItemPermissions);
+
+ }
+
+ private Permissions createPermissionsForBucketId(String bucketId, final Permissions knownPermissions) {
+
+ Authorizable bucketResource = authorizableLookup.getBucketAuthorizable(bucketId);
+
+ Permissions permissions = knownPermissions == null
+ ? authorizationService.getPermissionsForResource(bucketResource)
+ : authorizationService.getPermissionsForResource(bucketResource, knownPermissions);
+
+ return permissions;
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AnonymousIdentityFilter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AnonymousIdentityFilter.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AnonymousIdentityFilter.java
new file mode 100644
index 0000000..f879f0d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AnonymousIdentityFilter.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class AnonymousIdentityFilter extends AnonymousAuthenticationFilter {
+
+ private static final String ANONYMOUS_KEY = "anonymousNifiKey";
+
+ public AnonymousIdentityFilter() {
+ super(ANONYMOUS_KEY);
+ }
+
+ @Override
+ protected Authentication createAuthentication(HttpServletRequest request) {
+ return new AuthenticationSuccessToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationRequestToken.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationRequestToken.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationRequestToken.java
new file mode 100644
index 0000000..a5a5ec3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationRequestToken.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.security.Principal;
+import java.util.Collection;
+
+/**
+ * Wraps an AuthenticationRequest in a Token that implements the Spring Security Authentication interface.
+ */
+public class AuthenticationRequestToken implements Authentication {
+
+ private final AuthenticationRequest authenticationRequest;
+ private final Class<?> authenticationRequestOrigin;
+ private final String clientAddress;
+
+ public AuthenticationRequestToken(AuthenticationRequest authenticationRequest, Class<?> authenticationRequestOrigin, String clientAddress) {
+ this.authenticationRequest = authenticationRequest;
+ this.authenticationRequestOrigin = authenticationRequestOrigin;
+ this.clientAddress = clientAddress;
+ }
+
+ @Override
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ return null;
+ }
+
+ @Override
+ public Object getCredentials() {
+ return authenticationRequest.getCredentials();
+ }
+
+ @Override
+ public Object getDetails() {
+ return authenticationRequest.getDetails();
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return new Principal() {
+ @Override
+ public String getName() {
+ return authenticationRequest.getUsername();
+ }
+ };
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return false;
+ }
+
+ @Override
+ public void setAuthenticated(boolean b) throws IllegalArgumentException {
+ throw new IllegalArgumentException("AuthenticationRequestWrapper cannot be trusted. It is only to be used for storing an identity claim.");
+ }
+
+ @Override
+ public String getName() {
+ return authenticationRequest.getUsername();
+ }
+
+ @Override
+ public int hashCode() {
+ return authenticationRequest.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return authenticationRequest.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return authenticationRequest.toString();
+ }
+
+ public AuthenticationRequest getAuthenticationRequest() {
+ return authenticationRequest;
+ }
+
+ public Class<?> getAuthenticationRequestOrigin() {
+ return authenticationRequestOrigin;
+ }
+
+ public String getClientAddress() {
+ return clientAddress;
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationSuccessToken.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationSuccessToken.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationSuccessToken.java
new file mode 100644
index 0000000..ea6f1e9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/AuthenticationSuccessToken.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * An authentication token that represents an Authenticated and Authorized user of the NiFi Apis. The authorities are based off the specified UserDetails.
+ */
+public class AuthenticationSuccessToken extends AbstractAuthenticationToken {
+
+ private final UserDetails nifiUserDetails;
+
+ public AuthenticationSuccessToken(final UserDetails nifiUserDetails) {
+ super(nifiUserDetails.getAuthorities());
+ super.setAuthenticated(true);
+ setDetails(nifiUserDetails);
+ this.nifiUserDetails = nifiUserDetails;
+ }
+
+ @Override
+ public Object getCredentials() {
+ return nifiUserDetails.getPassword();
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return nifiUserDetails;
+ }
+
+ @Override
+ public final void setAuthenticated(boolean authenticated) {
+ throw new IllegalArgumentException("Cannot change the authenticated state.");
+ }
+
+ @Override
+ public String toString() {
+ return nifiUserDetails.getUsername();
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java
new file mode 100644
index 0000000..ff6a218
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationProvider.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.properties.util.IdentityMapping;
+import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
+import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
+import org.apache.nifi.registry.security.authentication.IdentityProvider;
+import org.apache.nifi.registry.security.authentication.exception.InvalidCredentialsException;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.ManagedAuthorizer;
+import org.apache.nifi.registry.security.authorization.UserAndGroups;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class IdentityAuthenticationProvider implements AuthenticationProvider {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(IdentityAuthenticationProvider.class);
+
+ protected NiFiRegistryProperties properties;
+ protected Authorizer authorizer;
+ protected final IdentityProvider identityProvider;
+ private List<IdentityMapping> mappings;
+
+ public IdentityAuthenticationProvider(
+ NiFiRegistryProperties properties,
+ Authorizer authorizer,
+ IdentityProvider identityProvider) {
+ this.properties = properties;
+ this.authorizer = authorizer;
+ this.identityProvider = identityProvider;
+ this.mappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+ // Determine if this AuthenticationProvider's identityProvider should be able to support this AuthenticationRequest
+ boolean tokenOriginatedFromThisIdentityProvider = checkTokenOriginatedFromThisIdentityProvider(authentication);
+
+ if (!tokenOriginatedFromThisIdentityProvider) {
+ // Returning null indicates to The Spring Security AuthenticationManager that this AuthenticationProvider
+ // cannot authenticate this token and another provider should be tried.
+ return null;
+ }
+
+ AuthenticationRequestToken authenticationRequestToken = ((AuthenticationRequestToken)authentication);
+ AuthenticationRequest authenticationRequest = authenticationRequestToken.getAuthenticationRequest();
+
+ try {
+ AuthenticationResponse authenticationResponse = identityProvider.authenticate(authenticationRequest);
+ if (authenticationResponse == null) {
+ return null;
+ }
+ return buildAuthenticatedToken(authenticationRequestToken, authenticationResponse);
+ } catch (InvalidCredentialsException e) {
+ throw new BadCredentialsException("Identity Provider authentication failed.", e);
+ }
+
+ }
+
+ @Override
+ public boolean supports(Class<?> authenticationClazz) {
+ // is authenticationClazz a subclass of AuthenticationRequestWrapper?
+ return AuthenticationRequestToken.class.isAssignableFrom(authenticationClazz);
+ }
+
+ protected AuthenticationSuccessToken buildAuthenticatedToken(
+ AuthenticationRequestToken requestToken,
+ AuthenticationResponse response) {
+
+ final String mappedIdentity = mapIdentity(response.getIdentity());
+
+ return new AuthenticationSuccessToken(new NiFiUserDetails(
+ new StandardNiFiUser.Builder()
+ .identity(mappedIdentity)
+ .groups(getUserGroups(mappedIdentity))
+ .clientAddress(requestToken.getClientAddress())
+ .build()));
+ }
+
+ protected boolean checkTokenOriginatedFromThisIdentityProvider(Authentication authentication) {
+ return (authentication instanceof AuthenticationRequestToken
+ && identityProvider.getClass().equals(((AuthenticationRequestToken) authentication).getAuthenticationRequestOrigin()));
+ }
+
+ protected String mapIdentity(final String identity) {
+ return IdentityMappingUtil.mapIdentity(identity, mappings);
+ }
+
+ protected Set<String> getUserGroups(final String identity) {
+ return getUserGroups(authorizer, identity);
+ }
+
+ private static Set<String> getUserGroups(final Authorizer authorizer, final String userIdentity) {
+ if (authorizer instanceof ManagedAuthorizer) {
+ final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
+ final UserGroupProvider userGroupProvider = managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+ final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(userIdentity);
+ final Set<Group> userGroups = userAndGroups.getGroups();
+
+ if (userGroups == null || userGroups.isEmpty()) {
+ return Collections.emptySet();
+ } else {
+ return userAndGroups.getGroups().stream().map(Group::getName).collect(Collectors.toSet());
+ }
+ } else {
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java
new file mode 100644
index 0000000..cd5e2bf
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication;
+
+import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
+import org.apache.nifi.registry.security.authentication.IdentityProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * A class that will extract an identity / credentials claim from an HttpServlet Request using an injected IdentityProvider.
+ *
+ * This class is designed to be used in collaboration with an {@link IdentityAuthenticationProvider}. The identity/credentials will be
+ * extracted by this filter and later validated by the {@link IdentityAuthenticationProvider} in the default SecurityInterceptorFilter.
+ */
+public class IdentityFilter extends GenericFilterBean {
+
+ private static final Logger logger = LoggerFactory.getLogger(IdentityFilter.class);
+
+ private final IdentityProvider identityProvider;
+
+ public IdentityFilter(IdentityProvider identityProvider) {
+ this.identityProvider = identityProvider;
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+
+ // Only require authentication from an identity provider if the NiFi registry is running securely.
+ if (!servletRequest.isSecure()) {
+ // Otherwise, requests will be "authenticated" by the AnonymousIdentityFilter
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+
+ if (identityProvider == null) {
+ logger.warn("Identity Filter configured with NULL identity provider. Credentials will not be extracted.");
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+
+ if (credentialsAlreadyPresent()) {
+ logger.debug("Credentials already extracted for [{}], skipping credentials extraction filter using {}",
+ SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString(),
+ identityProvider.getClass().getSimpleName());
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+
+ logger.debug("Attempting to extract user credentials using {}", identityProvider.getClass().getSimpleName());
+
+ try {
+ AuthenticationRequest authenticationRequest = identityProvider.extractCredentials((HttpServletRequest)servletRequest);
+ if (authenticationRequest != null) {
+ Authentication authentication = new AuthenticationRequestToken(authenticationRequest, identityProvider.getClass(), servletRequest.getRemoteAddr());
+ logger.debug("Adding credentials claim to SecurityContext to be authenticated. Credentials extracted by {}: {}",
+ identityProvider.getClass().getSimpleName(),
+ authenticationRequest);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ // This filter's job, which is merely to search for and extract an identity claim, is done.
+ // The actual authentication of the identity claim will be handled by a corresponding IdentityAuthenticationProvider
+ }
+ } catch (Exception e) {
+ logger.debug("Exception occurred while extracting credentials:", e);
+ }
+
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ private boolean credentialsAlreadyPresent() {
+ return SecurityContextHolder.getContext().getAuthentication() != null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java
new file mode 100644
index 0000000..016e9cb
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/InvalidAuthenticationException.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.exception;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * Thrown if the authentication of a given request is invalid. For instance,
+ * an expired certificate or token.
+ */
+public class InvalidAuthenticationException extends AuthenticationException {
+
+ public InvalidAuthenticationException(String msg) {
+ super(msg);
+ }
+
+ public InvalidAuthenticationException(String msg, Throwable t) {
+ super(msg, t);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
new file mode 100644
index 0000000..82570a3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.exception;
+
+import org.springframework.security.core.AuthenticationException;
+
+public class UntrustedProxyException extends AuthenticationException {
+
+ public UntrustedProxyException(String msg) {
+ super(msg);
+ }
+
+ public UntrustedProxyException(String msg, Throwable t) {
+ super(msg, t);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java
new file mode 100644
index 0000000..d3f12c9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.jwt;
+
+import io.jsonwebtoken.JwtException;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
+import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
+import org.apache.nifi.registry.security.authentication.BearerAuthIdentityProvider;
+import org.apache.nifi.registry.security.authentication.IdentityProvider;
+import org.apache.nifi.registry.security.authentication.IdentityProviderConfigurationContext;
+import org.apache.nifi.registry.security.authentication.exception.IdentityAccessException;
+import org.apache.nifi.registry.security.authentication.exception.InvalidCredentialsException;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class JwtIdentityProvider extends BearerAuthIdentityProvider implements IdentityProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(JwtIdentityProvider.class);
+
+ private static final String issuer = JwtIdentityProvider.class.getSimpleName();
+
+ private static final long expiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
+
+ private final JwtService jwtService;
+
+ @Autowired
+ public JwtIdentityProvider(JwtService jwtService, NiFiRegistryProperties nifiProperties, Authorizer authorizer) {
+ this.jwtService = jwtService;
+ }
+
+ @Override
+ public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) throws InvalidCredentialsException, IdentityAccessException {
+
+ if (authenticationRequest == null) {
+ logger.info("Cannot authenticate null authenticationRequest, returning null.");
+ return null;
+ }
+
+ final Object credentials = authenticationRequest.getCredentials();
+ String jwtAuthToken = credentials != null && credentials instanceof String ? (String) credentials : null;
+
+ if (credentials == null) {
+ logger.info("JWT not found in authenticationRequest credentials, returning null.");
+ return null;
+ }
+
+ try {
+ final String jwtPrincipal = jwtService.getAuthenticationFromToken(jwtAuthToken);
+ return new AuthenticationResponse(jwtPrincipal, jwtPrincipal, expiration, issuer);
+ } catch (JwtException e) {
+ throw new InvalidAuthenticationException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void onConfigured(IdentityProviderConfigurationContext configurationContext) throws SecurityProviderCreationException {}
+
+ @Override
+ public void preDestruction() throws SecurityProviderDestructionException {}
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
new file mode 100644
index 0000000..d47b301
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.SigningKeyResolverAdapter;
+import io.jsonwebtoken.UnsupportedJwtException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
+import org.apache.nifi.registry.security.key.Key;
+import org.apache.nifi.registry.security.key.KeyService;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+// TODO, look into replacing this JwtService service with Apache Licensed JJWT library
+@Service
+public class JwtService {
+
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class);
+
+ private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
+ private static final String KEY_ID_CLAIM = "kid";
+ private static final String USERNAME_CLAIM = "preferred_username";
+
+ private final KeyService keyService;
+
+ @Autowired
+ public JwtService(final KeyService keyService) {
+ this.keyService = keyService;
+ }
+
+ public String getAuthenticationFromToken(final String base64EncodedToken) throws JwtException {
+ // The library representations of the JWT should be kept internal to this service.
+ try {
+ final Jws<Claims> jws = parseTokenFromBase64EncodedString(base64EncodedToken);
+
+ if (jws == null) {
+ throw new JwtException("Unable to parse token");
+ }
+
+ // Additional validation that subject is present
+ if (StringUtils.isEmpty(jws.getBody().getSubject())) {
+ throw new JwtException("No subject available in token");
+ }
+
+ // TODO: Validate issuer against active IdentityProvider?
+ if (StringUtils.isEmpty(jws.getBody().getIssuer())) {
+ throw new JwtException("No issuer available in token");
+ }
+ return jws.getBody().getSubject();
+ } catch (JwtException e) {
+ logger.debug("The Base64 encoded JWT: " + base64EncodedToken);
+ final String errorMessage = "There was an error validating the JWT";
+ logger.error(errorMessage, e);
+ throw e;
+ }
+ }
+
+ private Jws<Claims> parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException {
+ try {
+ return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+ @Override
+ public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
+ final String identity = claims.getSubject();
+
+ // Get the key based on the key id in the claims
+ final String keyId = claims.get(KEY_ID_CLAIM, String.class);
+ final Key key = keyService.getKey(keyId);
+
+ // Ensure we were able to find a key that was previously issued by this key service for this user
+ if (key == null || key.getKey() == null) {
+ throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
+ }
+
+ return key.getKey().getBytes(StandardCharsets.UTF_8);
+ }
+ }).parseClaimsJws(base64EncodedToken);
+ } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException e) {
+ // TODO: Exercise all exceptions to ensure none leak key material to logs
+ final String errorMessage = "Unable to validate the access token.";
+ throw new JwtException(errorMessage, e);
+ }
+ }
+
+ /**
+ * Generates a signed JWT token from the provided IdentityProvider AuthenticationResponse
+ *
+ * @param authenticationResponse an instance issued by an IdentityProvider after identity claim has been verified as authentic
+ * @return a signed JWT containing the user identity and the identity provider, Base64-encoded
+ * @throws JwtException if there is a problem generating the signed token
+ */
+ public String generateSignedToken(final AuthenticationResponse authenticationResponse) throws JwtException {
+ if (authenticationResponse == null) {
+ throw new IllegalArgumentException("Cannot generate a JWT for a null authenticationResponse");
+ }
+
+ return generateSignedToken(
+ authenticationResponse.getIdentity(),
+ authenticationResponse.getUsername(),
+ authenticationResponse.getIssuer(),
+ authenticationResponse.getIssuer(),
+ authenticationResponse.getExpiration());
+ }
+
+ public String generateSignedToken(String identity, String preferredUsername, String issuer, String audience, long expirationMillis) throws JwtException {
+
+ if (identity == null || StringUtils.isEmpty(identity)) {
+ String errorMessage = "Cannot generate a JWT for a token with an empty identity";
+ errorMessage = issuer != null ? errorMessage + " issued by " + issuer + "." : ".";
+ logger.error(errorMessage);
+ throw new IllegalArgumentException(errorMessage);
+ }
+
+ // Compute expiration
+ final Calendar now = Calendar.getInstance();
+ long expirationMillisRelativeToNow = validateTokenExpiration(expirationMillis, identity);
+ long expirationMillisSinceEpoch = now.getTimeInMillis() + expirationMillisRelativeToNow;
+ final Calendar expiration = new Calendar.Builder().setInstant(expirationMillisSinceEpoch).build();
+
+ try {
+ // Get/create the key for this user
+ final Key key = keyService.getOrCreateKey(identity);
+ final byte[] keyBytes = key.getKey().getBytes(StandardCharsets.UTF_8);
+
+ //logger.trace("Generating JWT for " + describe(authenticationResponse));
+
+ // TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
+ // Build the token
+ return Jwts.builder().setSubject(identity)
+ .setIssuer(issuer)
+ .setAudience(audience)
+ .claim(USERNAME_CLAIM, preferredUsername)
+ .claim(KEY_ID_CLAIM, key.getId())
+ .setIssuedAt(now.getTime())
+ .setExpiration(expiration.getTime())
+ .signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
+ } catch (NullPointerException e) {
+ final String errorMessage = "Could not retrieve the signing key for JWT for " + identity;
+ logger.error(errorMessage, e);
+ throw new JwtException(errorMessage, e);
+ }
+
+ }
+
+ private static long validateTokenExpiration(long proposedTokenExpiration, String identity) {
+ final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
+ final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
+
+ if (proposedTokenExpiration > maxExpiration) {
+ logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", maxExpiration,
+ proposedTokenExpiration, identity));
+ proposedTokenExpiration = maxExpiration;
+ } else if (proposedTokenExpiration < minExpiration) {
+ logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", minExpiration,
+ proposedTokenExpiration, identity));
+ proposedTokenExpiration = minExpiration;
+ }
+
+ return proposedTokenExpiration;
+ }
+
+ private static String describe(AuthenticationResponse authenticationResponse) {
+ Calendar expirationTime = Calendar.getInstance();
+ expirationTime.setTimeInMillis(authenticationResponse.getExpiration());
+ long remainingTime = expirationTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS");
+ dateFormat.setTimeZone(expirationTime.getTimeZone());
+ String expirationTimeString = dateFormat.format(expirationTime.getTime());
+
+ return new StringBuilder("LoginAuthenticationToken for ")
+ .append(authenticationResponse.getUsername())
+ .append(" issued by ")
+ .append(authenticationResponse.getIssuer())
+ .append(" expiring at ")
+ .append(expirationTimeString)
+ .append(" [")
+ .append(authenticationResponse.getExpiration())
+ .append(" ms, ")
+ .append(remainingTime)
+ .append(" ms remaining]")
+ .toString();
+ }
+}