You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2017/10/05 18:11:28 UTC
[03/10] nifi-registry git commit: NIFIREG-9: Initial Auth
Implementation
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java
----------------------------------------------------------------------
diff --git a/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java
new file mode 100644
index 0000000..4950772
--- /dev/null
+++ b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java
@@ -0,0 +1,91 @@
+/*
+ * 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.util;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * A PropertyValue provides a mechanism whereby the currently configured value
+ * can be obtained in different forms.
+ * </p>
+ */
+public interface PropertyValue {
+
+ /**
+ * @return the raw property value as a string
+ */
+ String getValue();
+
+ /**
+ * @return an integer representation of the property value, or
+ * <code>null</code> if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Integer asInteger();
+
+ /**
+ * @return a Long representation of the property value, or <code>null</code>
+ * if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Long asLong();
+
+ /**
+ * @return a Boolean representation of the property value, or
+ * <code>null</code> if not set
+ */
+ Boolean asBoolean();
+
+ /**
+ * @return a Float representation of the property value, or
+ * <code>null</code> if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Float asFloat();
+
+ /**
+ * @return a Double representation of the property value, of
+ * <code>null</code> if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Double asDouble();
+
+ /**
+ * @param timeUnit specifies the TimeUnit to convert the time duration into
+ * @return a Long value representing the value of the configured time period
+ * in terms of the specified TimeUnit; if the property is not set, returns
+ * <code>null</code>
+ */
+ Long asTimePeriod(TimeUnit timeUnit);
+
+ /**
+ *
+ * @param dataUnit specifies the DataUnit to convert the data size into
+ * @return a Long value representing the value of the configured data size
+ * in terms of the specified DataUnit; if hte property is not set, returns
+ * <code>null</code>
+ */
+ Double asDataSize(DataUnit dataUnit);
+
+ /**
+ * @return <code>true</code> if the user has configured a value, or if the
+ * PropertyDescriptor for the associated property has a default
+ * value, <code>false</code> otherwise
+ */
+ boolean isSet();
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java
----------------------------------------------------------------------
diff --git a/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java
new file mode 100644
index 0000000..b185fad
--- /dev/null
+++ b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java
@@ -0,0 +1,79 @@
+/*
+ * 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.util;
+
+import java.util.concurrent.TimeUnit;
+
+public class StandardPropertyValue implements PropertyValue {
+
+ private final String rawValue;
+
+ public StandardPropertyValue(final String rawValue) {
+ this.rawValue = rawValue;
+ }
+
+ @Override
+ public String getValue() {
+ return rawValue;
+ }
+
+ @Override
+ public Integer asInteger() {
+ return (rawValue == null) ? null : Integer.parseInt(rawValue.trim());
+ }
+
+ @Override
+ public Long asLong() {
+ return (rawValue == null) ? null : Long.parseLong(rawValue.trim());
+ }
+
+ @Override
+ public Boolean asBoolean() {
+ return (rawValue == null) ? null : Boolean.parseBoolean(rawValue.trim());
+ }
+
+ @Override
+ public Float asFloat() {
+ return (rawValue == null) ? null : Float.parseFloat(rawValue.trim());
+ }
+
+ @Override
+ public Double asDouble() {
+ return (rawValue == null) ? null : Double.parseDouble(rawValue.trim());
+ }
+
+ @Override
+ public Long asTimePeriod(final TimeUnit timeUnit) {
+ return (rawValue == null) ? null : FormatUtils.getTimeDuration(rawValue.trim(), timeUnit);
+ }
+
+ @Override
+ public Double asDataSize(final DataUnit dataUnit) {
+ return rawValue == null ? null : DataUnit.parseDataSize(rawValue.trim(), dataUnit);
+ }
+
+ @Override
+ public boolean isSet() {
+ return rawValue != null;
+ }
+
+ @Override
+ public String toString() {
+ return rawValue;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/pom.xml b/nifi-registry-web-api/pom.xml
index 603cb68..4aa7f15 100644
--- a/nifi-registry-web-api/pom.xml
+++ b/nifi-registry-web-api/pom.xml
@@ -125,12 +125,17 @@
<artifactId>nifi-registry-framework</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- <!-- This will directly in lib so mark it as provided -->
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-properties</artifactId>
<version>0.0.1-SNAPSHOT</version>
- <scope>provided</scope>
+ <scope>provided</scope> <!-- This will be in the lib directory -->
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-security-api</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <scope>provided</scope> <!-- This will be in lib directory -->
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@@ -165,5 +170,25 @@
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt</artifactId>
+ <version>0.7.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>18.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <version>1.55</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <version>1.55</version>
+ </dependency>
</dependencies>
</project>
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
index 969f849..59452f9 100644
--- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
@@ -16,13 +16,24 @@
*/
package org.apache.nifi.registry.web;
+import org.apache.nifi.registry.web.api.AccessPolicyResource;
+import org.apache.nifi.registry.web.api.AccessResource;
import org.apache.nifi.registry.web.api.BucketFlowResource;
import org.apache.nifi.registry.web.api.BucketResource;
import org.apache.nifi.registry.web.api.FlowResource;
import org.apache.nifi.registry.web.api.ItemResource;
+import org.apache.nifi.registry.web.api.ResourceResource;
+import org.apache.nifi.registry.web.api.TenantResource;
+import org.apache.nifi.registry.web.mapper.AccessDeniedExceptionMapper;
+import org.apache.nifi.registry.web.mapper.AdministrationExceptionMapper;
+import org.apache.nifi.registry.web.mapper.AuthenticationCredentialsNotFoundExceptionMapper;
+import org.apache.nifi.registry.web.mapper.AuthorizationAccessExceptionMapper;
import org.apache.nifi.registry.web.mapper.IllegalArgumentExceptionMapper;
import org.apache.nifi.registry.web.mapper.IllegalStateExceptionMapper;
+import org.apache.nifi.registry.web.mapper.InvalidAuthenticationExceptionMapper;
+import org.apache.nifi.registry.web.mapper.NotFoundExceptionMapper;
import org.apache.nifi.registry.web.mapper.ResourceNotFoundExceptionMapper;
+import org.apache.nifi.registry.web.mapper.SerializationExceptionMapper;
import org.apache.nifi.registry.web.mapper.ThrowableMapper;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
@@ -48,17 +59,28 @@ public class NiFiRegistryResourceConfig extends ResourceConfig {
// register filters
register(HttpMethodOverrideFilter.class);
- // register the exception mappers
+ // register the exception mappers - TODO, see if these can be registered via scanning
+ register(new AccessDeniedExceptionMapper());
+ register(new AdministrationExceptionMapper());
+ register(new AuthenticationCredentialsNotFoundExceptionMapper());
+ register(new AuthorizationAccessExceptionMapper());
register(new IllegalArgumentExceptionMapper());
register(new IllegalStateExceptionMapper());
+ register(new InvalidAuthenticationExceptionMapper());
+ register(new NotFoundExceptionMapper());
register(new ResourceNotFoundExceptionMapper());
+ register(new SerializationExceptionMapper());
register(new ThrowableMapper());
// register endpoints
+ register(AccessPolicyResource.class);
+ register(AccessResource.class);
register(BucketResource.class);
register(BucketFlowResource.class);
- register(FlowResource.class);
register(ItemResource.class);
+ register(FlowResource.class);
+ register(ResourceResource.class);
+ register(TenantResource.class);
property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistrySecurityConfig.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistrySecurityConfig.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistrySecurityConfig.java
new file mode 100644
index 0000000..e9431d0
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistrySecurityConfig.java
@@ -0,0 +1,220 @@
+/*
+ * 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;
+
+import org.apache.nifi.registry.authorization.AuthorizableLookup;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.AuthorizerFactory;
+import org.apache.nifi.registry.authorization.StandardAuthorizableLookup;
+import org.apache.nifi.registry.authorization.StandardAuthorizerFactory;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.web.security.NiFiAnonymousUserFilter;
+import org.apache.nifi.registry.web.security.x509.SubjectDnX509PrincipalExtractor;
+import org.apache.nifi.registry.web.security.x509.X509AuthenticationFilter;
+import org.apache.nifi.registry.web.security.x509.X509AuthenticationProvider;
+import org.apache.nifi.registry.web.security.x509.X509CertificateExtractor;
+import org.apache.nifi.registry.web.security.x509.X509CertificateValidator;
+import org.apache.nifi.registry.web.security.x509.X509IdentityProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+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.web.authentication.AnonymousAuthenticationFilter;
+import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
+
+/**
+ * NiFi Web Api Spring security
+ */
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter {
+ private static final Logger logger = LoggerFactory.getLogger(NiFiRegistrySecurityConfig.class);
+
+ private NiFiRegistryProperties properties;
+
+ private X509AuthenticationFilter x509AuthenticationFilter;
+
+// private JwtAuthenticationFilter jwtAuthenticationFilter;
+// private JwtAuthenticationProvider jwtAuthenticationProvider;
+//
+// private OtpAuthenticationFilter otpAuthenticationFilter;
+// private OtpAuthenticationProvider otpAuthenticationProvider;
+
+ private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
+
+ public NiFiRegistrySecurityConfig() {
+ super(true); // disable defaults
+ }
+
+ @Override
+ public void configure(WebSecurity webSecurity) throws Exception {
+ // ignore the access endpoints for obtaining the access config, the access token
+ // granting, and access status for a given user (note: we are not ignoring the
+ // the /access/download-token and /access/ui-extension-token endpoints
+ webSecurity
+ .ignoring()
+ .antMatchers("/access", "/access/config", "/access/token", "/access/kerberos", "/access/oidc/**");
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .rememberMe().disable()
+ .authorizeRequests()
+ .anyRequest().fullyAuthenticated()
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+
+ // x509
+ http.addFilterBefore(x509AuthenticationFilter(), AnonymousAuthenticationFilter.class);
+
+ // jwt
+ // http.addFilterBefore(jwtFilterBean(), AnonymousAuthenticationFilter.class);
+
+ // otp
+ // http.addFilterBefore(otpFilterBean(), AnonymousAuthenticationFilter.class);
+
+ // anonymous
+ http.anonymous().authenticationFilter(anonymousFilter());
+ }
+
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ // override xxxBean method so the authentication manager is available in app context (necessary for the method level security)
+ return super.authenticationManagerBean();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth
+ .authenticationProvider(x509AuthenticationProvider());
+ /* TODO, add Jwt and Otp support */
+// .authenticationProvider(jwtAuthenticationProvider)
+// .authenticationProvider(otpAuthenticationProvider);
+ }
+
+// @Bean // TODO JwtAuthenticationFilter
+// public JwtAuthenticationFilter jwtFilterBean() throws Exception {
+// if (jwtAuthenticationFilter == null) {
+// jwtAuthenticationFilter = new JwtAuthenticationFilter();
+// jwtAuthenticationFilter.setProperties(properties);
+// jwtAuthenticationFilter.setAuthenticationManager(authenticationManager());
+// }
+// return jwtAuthenticationFilter;
+// }
+//
+// @Bean // TODO OtpAuthenticationFilter
+// public OtpAuthenticationFilter otpFilterBean() throws Exception {
+// if (otpAuthenticationFilter == null) {
+// otpAuthenticationFilter = new OtpAuthenticationFilter();
+// otpAuthenticationFilter.setProperties(properties);
+// otpAuthenticationFilter.setAuthenticationManager(authenticationManager());
+// }
+// return otpAuthenticationFilter;
+// }
+
+ @Bean
+ public X509AuthenticationFilter x509AuthenticationFilter() throws Exception {
+ if (x509AuthenticationFilter == null) {
+ x509AuthenticationFilter = new X509AuthenticationFilter();
+ x509AuthenticationFilter.setProperties(properties);
+ x509AuthenticationFilter.setCertificateExtractor(certificateExtractor());
+ x509AuthenticationFilter.setPrincipalExtractor(principalExtractor());
+ x509AuthenticationFilter.setAuthenticationManager(authenticationManager());
+ }
+ return x509AuthenticationFilter;
+ }
+
+ @Bean
+ public NiFiAnonymousUserFilter anonymousFilter() throws Exception {
+ if (anonymousAuthenticationFilter == null) {
+ anonymousAuthenticationFilter = new NiFiAnonymousUserFilter();
+ }
+ return anonymousAuthenticationFilter;
+ }
+
+ @Bean
+ public X509CertificateExtractor certificateExtractor() {
+ return new X509CertificateExtractor();
+ }
+
+ public X509CertificateValidator certificateValidator() {
+ return new X509CertificateValidator();
+ }
+
+ @Bean
+ public X509PrincipalExtractor principalExtractor() {
+ return new SubjectDnX509PrincipalExtractor();
+ }
+
+ @Bean
+ public X509IdentityProvider x509IdentityProvider() {
+ X509IdentityProvider x509IdentityProvider = new X509IdentityProvider();
+ x509IdentityProvider.setCertificateValidator(certificateValidator());
+ x509IdentityProvider.setPrincipalExtractor(principalExtractor());
+ return x509IdentityProvider;
+ }
+
+ @Bean
+ public X509AuthenticationProvider x509AuthenticationProvider() {
+ return new X509AuthenticationProvider(x509IdentityProvider(), authorizer(), this.properties);
+ }
+
+ @Bean(initMethod = "initialize")
+ public AuthorizerFactory authorizerFactory() {
+ return new StandardAuthorizerFactory(this.properties);
+ }
+
+ @Bean
+ public Authorizer authorizer() {
+ return authorizerFactory().getAuthorizer();
+ }
+
+ @Bean
+ public AuthorizableLookup authorizableLookup() {
+ return new StandardAuthorizableLookup();
+ }
+
+ @Autowired
+ public void setProperties(NiFiRegistryProperties properties) {
+ this.properties = properties;
+ }
+
+ // TODO, add Jwt and Otp support
+// @Autowired
+// public void setJwtAuthenticationProvider(JwtAuthenticationProvider jwtAuthenticationProvider) {
+// this.jwtAuthenticationProvider = jwtAuthenticationProvider;
+// }
+//
+// @Autowired
+// public void setOtpAuthenticationProvider(OtpAuthenticationProvider otpAuthenticationProvider) {
+// this.otpAuthenticationProvider = otpAuthenticationProvider;
+// }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java
new file mode 100644
index 0000000..d72aa94
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java
@@ -0,0 +1,344 @@
+/*
+ * 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.api;
+
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.authorization.resource.Authorizable;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.model.authorization.AccessPolicy;
+import org.apache.nifi.registry.model.authorization.AccessPolicySummary;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * RESTful endpoint for managing access policies.
+ */
+@Component
+@Path("/policies")
+@Api(
+ value = "/policies",
+ description = "Endpoint for managing access policies."
+)
+public class AccessPolicyResource extends AuthorizableApplicationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(AccessPolicyResource.class);
+
+ @Autowired
+ public AccessPolicyResource(
+ Authorizer authorizer,
+ AuthorizationService authorizationService) {
+ super(authorizer, authorizationService);
+ }
+
+ // TODO - Verify that access control is done by the resource the policy is for, not the /policies resource itself.
+
+ /**
+ * Create a new access policy.
+ *
+ * @param httpServletRequest request
+ * @param requestAccessPolicy the access policy to create.
+ * @return The created access policy.
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Creates an access policy",
+ response = AccessPolicy.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response createAccessPolicy(
+ @Context final HttpServletRequest httpServletRequest,
+ @ApiParam(value = "The access policy configuration details.", required = true)
+ final AccessPolicy requestAccessPolicy) {
+
+ verifyAuthorizerSupportsConfigurablePolicies();
+
+ if (requestAccessPolicy == null) {
+ throw new IllegalArgumentException("Access policy details must be specified when creating a new policy.");
+ }
+ if (requestAccessPolicy.getIdentifier() != null) {
+ throw new IllegalArgumentException("Access policy ID cannot be specified when creating a new policy.");
+ }
+ if (requestAccessPolicy.getResource() == null) {
+ throw new IllegalArgumentException("Resource must be specified when creating a new access policy.");
+ }
+ RequestAction.valueOfValue(requestAccessPolicy.getAction());
+
+ authorizeAccessToResource(RequestAction.WRITE, requestAccessPolicy.getResource());
+
+ AccessPolicy createdPolicy = authorizationService.createAccessPolicy(requestAccessPolicy);
+
+ String locationUri = generateAccessPolicyUri(createdPolicy);
+ return generateCreatedResponse(URI.create(locationUri), createdPolicy).build();
+ }
+
+ /**
+ * Retrieves all access policies
+ *
+ * @return A list of access policies
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Gets all access policies",
+ response = AccessPolicy.class,
+ responseContainer = "List"
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_409),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getAccessPolicies() {
+
+ verifyAuthorizerIsManaged();
+ authorizeAccess(RequestAction.READ);
+
+ final List<AccessPolicy> accessPolicies = authorizationService.getAccessPolicies();
+
+ return generateOkResponse(accessPolicies).build();
+ }
+
+ /**
+ * Retrieves the specified access policy.
+ *
+ * @param identifier The id of the access policy to retrieve
+ * @return An accessPolicyEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ @ApiOperation(
+ value = "Gets an access policy",
+ response = AccessPolicy.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404) })
+ public Response getAccessPolicy(
+ @ApiParam(value = "The access policy id.", required = true)
+ @PathParam("id") final String identifier) {
+
+ verifyAuthorizerIsManaged();
+
+ final AccessPolicy accessPolicy = authorizationService.getAccessPolicy(identifier);
+ authorizeAccessToResource(RequestAction.READ, accessPolicy.getResource());
+
+ return generateOkResponse(accessPolicy).build();
+ }
+
+
+ /**
+ * Retrieve a specified access policy for a given (action, resource) pair.
+ *
+ * @param action the action, i.e. "read", "write"
+ * @param rawResource the name of the resource as a raw string
+ * @return An access policy.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{action}/{resource: .+}")
+ @ApiOperation(
+ value = "Gets an access policy for the specified action and resource",
+ notes = "Will return the effective policy if no specific policy exists for the specified action and resource. "
+ + "Must have Read permissions to the policy with the desired action and resource. Permissions for the policy that is "
+ + "returned will be indicated in the response. If the client does not have permissions to that policy, the response "
+ + "will not include the policy and the permissions in the response will be marked accordingly. If the client does "
+ + "not have permissions to the policy of the desired action and resource a 403 response will be returned.",
+ response = AccessPolicy.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getAccessPolicyForResource(
+ @ApiParam(value = "The request action.", allowableValues = "read, write" /* todo, +delete */, required = true)
+ @PathParam("action")
+ final String action,
+ @ApiParam(value = "The resource of the policy.", required = true)
+ @PathParam("resource")
+ final String rawResource) {
+
+ verifyAuthorizerIsManaged();
+
+ // parse the action and resource type
+ final RequestAction requestAction = RequestAction.valueOfValue(action);
+ final String resource = "/" + rawResource;
+
+ authorizeAccessToResource(RequestAction.READ, resource);
+
+ AccessPolicy accessPolicy = authorizationService.getAccessPolicy(resource, requestAction);
+ return generateOkResponse(accessPolicy).build();
+ }
+
+
+ /**
+ * Update an access policy.
+ *
+ * @param httpServletRequest request
+ * @param identifier The id of the access policy to update.
+ * @param requestAccessPolicy An access policy.
+ * @return the updated access policy.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ @ApiOperation(
+ value = "Updates a access policy",
+ response = AccessPolicy.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response updateAccessPolicy(
+ @Context
+ final HttpServletRequest httpServletRequest,
+ @ApiParam(value = "The access policy id.", required = true)
+ @PathParam("id")
+ final String identifier,
+ @ApiParam(value = "The access policy configuration details.", required = true)
+ final AccessPolicy requestAccessPolicy) {
+
+ verifyAuthorizerSupportsConfigurablePolicies();
+
+ if (requestAccessPolicy == null) {
+ throw new IllegalArgumentException("Access policy details must be specified when updating a policy.");
+ }
+ if (!identifier.equals(requestAccessPolicy.getIdentifier())) {
+ throw new IllegalArgumentException(String.format("The policy id in the request body (%s) does not equal the "
+ + "policy id of the requested resource (%s).", requestAccessPolicy.getIdentifier(), identifier));
+ }
+
+ authorizeAccessToPolicy(RequestAction.WRITE, identifier);
+
+ AccessPolicy createdPolicy = authorizationService.createAccessPolicy(requestAccessPolicy);
+
+ String locationUri = generateAccessPolicyUri(createdPolicy);
+ return generateCreatedResponse(URI.create(locationUri), createdPolicy).build();
+ }
+
+
+ /**
+ * Remove a specified access policy.
+ *
+ * @param httpServletRequest request
+ * @param identifier The id of the access policy to remove.
+ * @return The deleted access policy
+ */
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ @ApiOperation(
+ value = "Deletes an access policy",
+ response = AccessPolicy.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found.") })
+ public Response removeAccessPolicy(
+ @Context final HttpServletRequest httpServletRequest,
+ @ApiParam(value = "The access policy id.", required = true)
+ @PathParam("id")
+ final String identifier) {
+
+ verifyAuthorizerSupportsConfigurablePolicies();
+ authorizeAccessToPolicy(RequestAction.WRITE, identifier);
+ AccessPolicy deletedPolicy = authorizationService.deleteAccessPolicy(identifier);
+ return generateOkResponse(deletedPolicy).build();
+ }
+
+
+ private void verifyAuthorizerIsManaged() {
+ if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+ throw new IllegalStateException(AuthorizationService.MSG_NON_MANAGED_AUTHORIZER);
+ }
+ }
+
+ private void verifyAuthorizerSupportsConfigurablePolicies() {
+ if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
+ throw new IllegalStateException(AuthorizationService.MSG_NON_CONFIGURABLE_POLICIES);
+ }
+ }
+
+ private void authorizeAccess(RequestAction actionType) {
+ authorizationService.authorizeAccess(lookup -> {
+ final Authorizable policiesAuthorizable = lookup.getPoliciesAuthorizable();
+ policiesAuthorizable.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser());
+ });
+ }
+
+ private void authorizeAccessToPolicy(RequestAction actionType, String accessPolicyIdentifier) {
+ AccessPolicy accessPolicy = authorizationService.getAccessPolicy(accessPolicyIdentifier);
+ authorizeAccessToResource(actionType, accessPolicy.getResource());
+ }
+
+ private void authorizeAccessToResource(RequestAction actionType, String resource) {
+ authorizationService.authorizeAccess(lookup -> {
+ final Authorizable accessPolicy = lookup.getAccessPolicyByResource(resource);
+ accessPolicy.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser());
+ });
+ }
+
+ private String generateAccessPolicyUri(final AccessPolicySummary accessPolicy) {
+ return generateResourceUri("policies", accessPolicy.getIdentifier());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java
new file mode 100644
index 0000000..a5c849a
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java
@@ -0,0 +1,164 @@
+/*
+ * 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.api;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.nifi.registry.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.authorization.user.NiFiUser;
+import org.apache.nifi.registry.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.exception.AdministrationException;
+import org.apache.nifi.registry.model.authorization.AccessStatus;
+import org.apache.nifi.registry.web.security.InvalidAuthenticationException;
+import org.apache.nifi.registry.web.security.ProxiedEntitiesUtils;
+import org.apache.nifi.registry.web.security.UntrustedProxyException;
+import org.apache.nifi.registry.web.security.token.NiFiAuthenticationToken;
+import org.apache.nifi.registry.web.security.x509.X509AuthenticationProvider;
+import org.apache.nifi.registry.web.security.x509.X509AuthenticationRequestToken;
+import org.apache.nifi.registry.web.security.x509.X509CertificateExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.security.cert.X509Certificate;
+
+@Component
+@Path("/access")
+@Api(
+ value = "/access",
+ description = "Endpoints for obtaining an access token or checking access status."
+)
+public class AccessResource extends ApplicationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(AccessResource.class);
+
+ private X509CertificateExtractor certificateExtractor;
+ private X509AuthenticationProvider x509AuthenticationProvider;
+ private X509PrincipalExtractor x509principalExtractor;
+
+ public AccessResource(X509CertificateExtractor certificateExtractor, X509AuthenticationProvider x509AuthenticationProvider, X509PrincipalExtractor x509principalExtractor) {
+ this.certificateExtractor = certificateExtractor;
+ this.x509AuthenticationProvider = x509AuthenticationProvider;
+ this.x509principalExtractor = x509principalExtractor;
+ }
+
+ /**
+ * Gets the status the client's access.
+ *
+ * @param httpServletRequest the servlet request
+ * @return A accessStatusEntity
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("")
+ @ApiOperation(
+ value = "Gets the status the client's access",
+ response = AccessStatus.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getAccessStatus(@Context HttpServletRequest httpServletRequest) {
+ // only consider user specific access over https
+ if (!httpServletRequest.isSecure()) {
+ throw new IllegalStateException("User authentication/authorization is only supported when running over HTTPS.");
+ }
+
+ final AccessStatus accessStatus = new AccessStatus();
+
+ try {
+ final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest);
+
+ // if there is not certificate, consider a token
+ if (certificates == null) {
+
+ // TODO - add JWT Authentication support
+ throw new NotImplementedException("NiFi Registry client is trying to authentication with something other than a client cert. " +
+ "At this time, only client certificate authentication is supported.");
+
+// // look for an authorization token
+// final String authorization = httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION);
+//
+// // if there is no authorization header, we don't know the user
+// if (authorization == null) {
+// accessStatus.setStatus(AccessStatus.Status.UNKNOWN.name());
+// accessStatus.setMessage("No credentials supplied, unknown user.");
+// } else {
+// try {
+// // Extract the Base64 encoded token from the Authorization header
+// final String token = StringUtils.substringAfterLast(authorization, " ");
+//
+// final JwtAuthenticationRequestToken jwtRequest = new JwtAuthenticationRequestToken(token, httpServletRequest.getRemoteAddr());
+// final NiFiAuthenticationToken authenticationResponse = (NiFiAuthenticationToken) jwtAuthenticationProvider.authenticate(jwtRequest);
+// final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();
+//
+// // set the user identity
+// accessStatus.setIdentity(nifiUser.getIdentity());
+//
+// // attempt authorize to /flow
+// accessStatus.setStatus(AccessStatus.Status.ACTIVE.name());
+// accessStatus.setMessage("You are already logged in.");
+// } catch (JwtException e) {
+// throw new InvalidAuthenticationException(e.getMessage(), e);
+// }
+// }
+ } else {
+ try {
+ final X509AuthenticationRequestToken x509Request = new X509AuthenticationRequestToken(
+ httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), x509principalExtractor, certificates, httpServletRequest.getRemoteAddr());
+
+ final NiFiAuthenticationToken authenticationResponse = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(x509Request);
+ final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();
+
+ // set the user identity
+ accessStatus.setIdentity(nifiUser.getIdentity());
+
+ // attempt authorize to /flow
+ accessStatus.setStatus(AccessStatus.Status.ACTIVE.name());
+ accessStatus.setMessage("You are already logged in.");
+ } catch (final IllegalArgumentException iae) {
+ throw new InvalidAuthenticationException(iae.getMessage(), iae);
+ }
+ }
+ } catch (final UntrustedProxyException upe) {
+ throw new AccessDeniedException(upe.getMessage(), upe);
+ } catch (final AuthenticationServiceException ase) {
+ throw new AdministrationException(ase.getMessage(), ase);
+ }
+
+ return generateOkResponse(accessStatus).build();
+ }
+
+
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java
new file mode 100644
index 0000000..6c4c3ae
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java
@@ -0,0 +1,175 @@
+/*
+ * 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.api;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriBuilderException;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class ApplicationResource {
+
+ public static final String PROXY_SCHEME_HTTP_HEADER = "X-ProxyScheme";
+ public static final String PROXY_HOST_HTTP_HEADER = "X-ProxyHost";
+ public static final String PROXY_PORT_HTTP_HEADER = "X-ProxyPort";
+ public static final String PROXY_CONTEXT_PATH_HTTP_HEADER = "X-ProxyContextPath";
+
+ public static final String FORWARDED_PROTO_HTTP_HEADER = "X-Forwarded-Proto";
+ public static final String FORWARDED_HOST_HTTP_HEADER = "X-Forwarded-Server";
+ public static final String FORWARDED_PORT_HTTP_HEADER = "X-Forwarded-Port";
+ public static final String FORWARDED_CONTEXT_HTTP_HEADER = "X-Forwarded-Context";
+
+ protected static final String NON_GUARANTEED_ENDPOINT = "Note: This endpoint is subject to change as NiFi and its REST API evolve.";
+
+ private static final Logger logger = LoggerFactory.getLogger(ApplicationResource.class);
+
+ @Context
+ private HttpServletRequest httpServletRequest;
+
+ @Context
+ private UriInfo uriInfo;
+
+ protected String generateResourceUri(final String... path) {
+ final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.segment(path);
+ URI uri = uriBuilder.build();
+ try {
+
+ // check for proxy settings
+ final String scheme = getFirstHeaderValue(PROXY_SCHEME_HTTP_HEADER, FORWARDED_PROTO_HTTP_HEADER);
+ final String host = getFirstHeaderValue(PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER);
+ final String port = getFirstHeaderValue(PROXY_PORT_HTTP_HEADER, FORWARDED_PORT_HTTP_HEADER);
+ String baseContextPath = getFirstHeaderValue(PROXY_CONTEXT_PATH_HTTP_HEADER, FORWARDED_CONTEXT_HTTP_HEADER);
+
+ // if necessary, prepend the context path
+ String resourcePath = uri.getPath();
+ if (baseContextPath != null) {
+ // normalize context path
+ if (!baseContextPath.startsWith("/")) {
+ baseContextPath = "/" + baseContextPath;
+ }
+
+ if (baseContextPath.endsWith("/")) {
+ baseContextPath = StringUtils.substringBeforeLast(baseContextPath, "/");
+ }
+
+ // determine the complete resource path
+ resourcePath = baseContextPath + resourcePath;
+ }
+
+ // determine the port uri
+ int uriPort = uri.getPort();
+ if (port != null) {
+ if (StringUtils.isWhitespace(port)) {
+ uriPort = -1;
+ } else {
+ try {
+ uriPort = Integer.parseInt(port);
+ } catch (final NumberFormatException nfe) {
+ logger.warn(String.format("Unable to parse proxy port HTTP header '%s'. Using port from request URI '%s'.", port, uriPort));
+ }
+ }
+ }
+
+ // construct the URI
+ uri = new URI(
+ (StringUtils.isBlank(scheme)) ? uri.getScheme() : scheme,
+ uri.getUserInfo(),
+ (StringUtils.isBlank(host)) ? uri.getHost() : host,
+ uriPort,
+ resourcePath,
+ uri.getQuery(),
+ uri.getFragment());
+
+ } catch (final URISyntaxException use) {
+ throw new UriBuilderException(use);
+ }
+ return uri.toString();
+ }
+
+ /**
+ * Edit the response headers to indicating no caching.
+ *
+ * @param response response
+ * @return builder
+ */
+ protected Response.ResponseBuilder noCache(final Response.ResponseBuilder response) {
+ final CacheControl cacheControl = new CacheControl();
+ cacheControl.setPrivate(true);
+ cacheControl.setNoCache(true);
+ cacheControl.setNoStore(true);
+ return response.cacheControl(cacheControl);
+ }
+
+ /**
+ * Generates an OK response with the specified content.
+ *
+ * @param entity The entity
+ * @return The response to be built
+ */
+ protected Response.ResponseBuilder generateOkResponse(final Object entity) {
+ final Response.ResponseBuilder response = Response.ok(entity);
+ return noCache(response);
+ }
+
+ /**
+ * Generates a 201 Created response with the specified content.
+ *
+ * @param uri The URI
+ * @param entity entity
+ * @return The response to be built
+ */
+ protected Response.ResponseBuilder generateCreatedResponse(final URI uri, final Object entity) {
+ // generate the response builder
+ return Response.created(uri).entity(entity);
+ }
+
+ /**
+ * Returns the value for the first key discovered when inspecting the current request. Will
+ * return null if there are no keys specified or if none of the specified keys are found.
+ *
+ * @param keys http header keys
+ * @return the value for the first key found
+ */
+ private String getFirstHeaderValue(final String... keys) {
+ if (keys == null) {
+ return null;
+ }
+
+ for (final String key : keys) {
+ final String value = httpServletRequest.getHeader(key);
+
+ // if we found an entry for this key, return the value
+ if (value != null) {
+ return value;
+ }
+ }
+
+ // unable to find any matching keys
+ return null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java
new file mode 100644
index 0000000..d15685d
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java
@@ -0,0 +1,82 @@
+/*
+ * 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.api;
+
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.authorization.resource.Authorizable;
+import org.apache.nifi.registry.authorization.resource.ResourceType;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.model.authorization.Resource;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class AuthorizableApplicationResource extends ApplicationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthorizableApplicationResource.class);
+
+ protected final AuthorizationService authorizationService;
+ protected final Authorizer authorizer;
+
+ protected AuthorizableApplicationResource(
+ Authorizer authorizer,
+ AuthorizationService authorizationService) {
+ this.authorizer = authorizer;
+ this.authorizationService = authorizationService;
+ }
+
+ protected void authorizeBucketAccess(RequestAction actionType, String bucketIdentifier) {
+ authorizationService.authorizeAccess(lookup -> {
+ final Authorizable bucketAccessPolicy = lookup.getBucketAuthorizable(bucketIdentifier);
+ bucketAccessPolicy.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser());
+ });
+ }
+
+ protected void authorizeBucketItemAccess(RequestAction actionType, BucketItem bucketItem) {
+ authorizeBucketAccess(actionType, bucketItem.getBucketIdentifier());
+ }
+
+ protected Set<String> getAuthorizedBucketIds() {
+ return authorizationService
+ .getAuthorizedResources(RequestAction.READ, ResourceType.Bucket)
+ .stream()
+ .map(AuthorizableApplicationResource::extractBucketIdFromResource)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toSet());
+ }
+
+ private static String extractBucketIdFromResource(Resource resource) {
+
+ if (resource == null || resource.getIdentifier() == null || !resource.getIdentifier().startsWith("/buckets/")) {
+ return null;
+ }
+
+ String[] pathComponents = resource.getIdentifier().split("/");
+ if (pathComponents.length < 3) {
+ return null;
+ }
+ return pathComponents[2];
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java
index 7933e87..a42a739 100644
--- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java
@@ -18,20 +18,43 @@ package org.apache.nifi.registry.web.api;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.exception.ResourceNotFoundException;
import org.apache.nifi.registry.flow.VersionedFlow;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.service.AuthorizationService;
import org.apache.nifi.registry.service.RegistryService;
+import org.apache.nifi.registry.service.params.QueryParameters;
+import org.apache.nifi.registry.service.params.SortParameter;
+import org.apache.nifi.registry.web.link.LinkService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.util.List;
+import java.util.SortedSet;
@Component
@Path("/buckets/{bucketId}/flows")
@@ -39,14 +62,22 @@ import javax.ws.rs.core.Response;
value = "bucket >> flows",
description = "Create flows scoped to an existing bucket in the registry."
)
-public class BucketFlowResource {
+public class BucketFlowResource extends AuthorizableApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(BucketFlowResource.class);
private final RegistryService registryService;
+ private final LinkService linkService;
- public BucketFlowResource(@Autowired final RegistryService registryService) {
+ @Autowired
+ public BucketFlowResource(
+ final RegistryService registryService,
+ final LinkService linkService,
+ final AuthorizationService authorizationService,
+ final Authorizer authorizer) {
+ super(authorizer, authorizationService);
this.registryService = registryService;
+ this.linkService = linkService;
}
@POST
@@ -57,22 +88,320 @@ public class BucketFlowResource {
"The flow id is created by the server and a location URI for the created flow resource is returned.",
response = VersionedFlow.class
)
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
public Response createFlow(@PathParam("bucketId") final String bucketId, final VersionedFlow flow) {
+ authorizeBucketAccess(RequestAction.WRITE, bucketId);
+ verifyPathParamsMatchBody(bucketId, flow);
final VersionedFlow createdFlow = registryService.createFlow(bucketId, flow);
return Response.status(Response.Status.OK).entity(createdFlow).build();
}
- /* TODO, add redirection URIs so that GET, PUT, DELETE operations for a given flow id (once created)
- * are accessible as a subresource from /buckets as well */
-// @GET
-// @PathParam("/{bucketId}/flows/{flowSubpath}")
-// @ApiOperation("Redirects to /flows/{flowSubpath}")
-// public Response getFlowAlias(
-// @PathParam("bucketId") String bucketId,
-// @PathParam("flowSubpath") String flowSubpath) {
-// logger.info("Redirecting flow operation on bucket resource handler to flow resource handler.");
-// UriBuilder addressBuilder = uriInfo.getBaseUriBuilder();
-// addressBuilder.path("flows/" + flowSubpath);
-// return Response.seeOther(addressBuilder.build()).build();
-// }
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Get metadata for all flows in all buckets that the registry has stored for which the client is authorized. The information about " +
+ "the versions of each flow should be obtained by requesting a specific flow by id.",
+ response = VersionedFlow.class,
+ responseContainer = "List"
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getFlows(
+ @PathParam("bucketId") final String bucketId,
+ @ApiParam(value = SortParameter.API_PARAM_DESCRIPTION, format = "field:order", allowMultiple = true, example = "name:ASC")
+ @QueryParam("sort")
+ final List<String> sortParameters) {
+ authorizeBucketAccess(RequestAction.READ, bucketId);
+
+ final QueryParameters.Builder paramsBuilder = new QueryParameters.Builder();
+ for (String sortParam : sortParameters) {
+ paramsBuilder.addSort(SortParameter.fromString(sortParam));
+ }
+
+ final List<VersionedFlow> flows = registryService.getFlows(bucketId);
+ linkService.populateFlowLinks(flows);
+
+ return Response.status(Response.Status.OK).entity(flows).build();
+ }
+
+ @GET
+ @Path("{flowId}")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Get metadata for an existing flow the registry has stored. If verbose is true, then the metadata " +
+ "about all snapshots for the flow will also be returned.",
+ response = VersionedFlow.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getFlow(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId,
+ @QueryParam("verbose") @DefaultValue("false") boolean verbose) {
+ authorizeBucketAccess(RequestAction.READ, bucketId);
+
+ final VersionedFlow flow = registryService.getFlow(bucketId, flowId, verbose);
+
+ linkService.populateFlowLinks(flow);
+
+ if (flow.getSnapshotMetadata() != null) {
+ linkService.populateSnapshotLinks(flow.getSnapshotMetadata());
+ }
+
+ return Response.status(Response.Status.OK).entity(flow).build();
+ }
+
+ @PUT
+ @Path("{flowId}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Update an existing flow the registry has stored.",
+ response = VersionedFlow.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response updateFlow(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId,
+ final VersionedFlow flow) {
+
+ verifyPathParamsMatchBody(bucketId, flowId, flow);
+ setBucketItemMetadataIfMissing(bucketId, flowId, flow);
+
+ authorizeBucketAccess(RequestAction.WRITE, bucketId);
+
+ final VersionedFlow updatedFlow = registryService.updateFlow(flow);
+ return Response.status(Response.Status.OK).entity(updatedFlow).build();
+ }
+
+ @DELETE
+ @Path("{flowId}")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Delete an existing flow the registry has stored.",
+ response = VersionedFlow.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response deleteFlow(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId) {
+
+ authorizeBucketAccess(RequestAction.WRITE, bucketId);
+ final VersionedFlow deletedFlow = registryService.deleteFlow(bucketId, flowId);
+ return Response.status(Response.Status.OK).entity(deletedFlow).build();
+ }
+
+ @POST
+ @Path("{flowId}/versions")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Create the next version of a given flow ID. " +
+ "The version number is created by the server and a location URI for the created version resource is returned.",
+ response = VersionedFlowSnapshot.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response createFlowVersion(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId,
+ final VersionedFlowSnapshot snapshot) {
+ verifyPathParamsMatchBody(bucketId, flowId, snapshot);
+ authorizeBucketAccess(RequestAction.WRITE, bucketId);
+
+ setSnaphotMetadataIfMissing(bucketId, flowId, snapshot);
+ final VersionedFlowSnapshot createdSnapshot = registryService.createFlowSnapshot(snapshot);
+ return Response.status(Response.Status.OK).entity(createdSnapshot).build();
+ }
+
+ @GET
+ @Path("{flowId}/versions")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Get summary of all versions of a flow for a given flow ID.",
+ response = VersionedFlowSnapshotMetadata.class,
+ responseContainer = "List"
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getFlowVersions(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId) {
+ authorizeBucketAccess(RequestAction.READ, bucketId);
+ final VersionedFlow flow = registryService.getFlow(bucketId, flowId, true);
+
+ if (flow.getSnapshotMetadata() != null) {
+ linkService.populateSnapshotLinks(flow.getSnapshotMetadata());
+ }
+
+ return Response.status(Response.Status.OK).entity(flow.getSnapshotMetadata()).build();
+ }
+
+ @GET
+ @Path("{flowId}/versions/latest")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Get the latest version of a flow for a given flow ID",
+ response = VersionedFlowSnapshot.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getLatestFlowVersion(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId) {
+ authorizeBucketAccess(RequestAction.READ, bucketId);
+ final VersionedFlow flow = registryService.getFlow(bucketId, flowId, true);
+
+ final SortedSet<VersionedFlowSnapshotMetadata> snapshots = flow.getSnapshotMetadata();
+ if (snapshots == null || snapshots.size() == 0) {
+ throw new ResourceNotFoundException("Not flow versions found for flow with id " + flowId);
+ }
+
+ final VersionedFlowSnapshotMetadata lastSnapshotMetadata = snapshots.last();
+ final VersionedFlowSnapshot lastSnapshot = registryService.getFlowSnapshot(bucketId, flowId, lastSnapshotMetadata.getVersion());
+
+ return Response.status(Response.Status.OK).entity(lastSnapshot).build();
+ }
+
+ @GET
+ @Path("{flowId}/versions/{versionNumber: \\d+}")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Get a given version of a flow for a given flow ID",
+ response = VersionedFlowSnapshot.class
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getFlowVersion(
+ @PathParam("bucketId") final String bucketId,
+ @PathParam("flowId") final String flowId,
+ @PathParam("versionNumber") final Integer versionNumber) {
+ authorizeBucketAccess(RequestAction.READ, bucketId);
+ final VersionedFlowSnapshot snapshot = registryService.getFlowSnapshot(bucketId, flowId, versionNumber);
+ return Response.status(Response.Status.OK).entity(snapshot).build();
+ }
+
+ private static void verifyPathParamsMatchBody(String bucketIdParam, BucketItem bodyBucketItem) throws BadRequestException {
+ if (StringUtils.isBlank(bucketIdParam)) {
+ throw new BadRequestException("Bucket id path parameter cannot be blank");
+ }
+
+ if (bodyBucketItem == null) {
+ throw new BadRequestException("Object in body cannot be null");
+ }
+
+ if (bodyBucketItem.getBucketIdentifier() != null && !bucketIdParam.equals(bodyBucketItem.getBucketIdentifier())) {
+ throw new BadRequestException("Bucket id in path param must match bucket id in body");
+ }
+ }
+
+ private static void verifyPathParamsMatchBody(String bucketIdParam, String flowIdParam, BucketItem bodyBucketItem) throws BadRequestException {
+ verifyPathParamsMatchBody(bucketIdParam, bodyBucketItem);
+
+ if (StringUtils.isBlank(flowIdParam)) {
+ throw new BadRequestException("Flow id path parameter cannot be blank");
+ }
+
+ if (bodyBucketItem.getIdentifier() != null && !flowIdParam.equals(bodyBucketItem.getIdentifier())) {
+ throw new BadRequestException("Item id in path param must match item id in body");
+ }
+ }
+
+ private static void verifyPathParamsMatchBody(String bucketIdParam, String flowIdParam, VersionedFlowSnapshot flowSnapshot) throws BadRequestException {
+ if (StringUtils.isBlank(bucketIdParam)) {
+ throw new BadRequestException("Bucket id path parameter cannot be blank");
+ }
+
+ if (StringUtils.isBlank(flowIdParam)) {
+ throw new BadRequestException("Flow id path parameter cannot be blank");
+ }
+
+ if (flowSnapshot == null) {
+ throw new BadRequestException("VersionedFlowSnapshot cannot be null in body");
+ }
+
+ final VersionedFlowSnapshotMetadata metadata = flowSnapshot.getSnapshotMetadata();
+ if (metadata != null && metadata.getBucketIdentifier() != null && !bucketIdParam.equals(metadata.getBucketIdentifier())) {
+ throw new BadRequestException("Bucket id in path param must match bucket id in body");
+ }
+ if (metadata != null && metadata.getFlowIdentifier() != null && !flowIdParam.equals(metadata.getFlowIdentifier())) {
+ throw new BadRequestException("Flow id in path param must match flow id in body");
+ }
+ }
+
+ private static void setBucketItemMetadataIfMissing(
+ @NotNull String bucketIdParam,
+ @NotNull String bucketItemIdParam,
+ @NotNull BucketItem bucketItem) {
+ if (bucketItem.getBucketIdentifier() == null) {
+ bucketItem.setBucketIdentifier(bucketIdParam);
+ }
+
+ if (bucketItem.getIdentifier() == null) {
+ bucketItem.setIdentifier(bucketItemIdParam);
+ }
+ }
+
+ private static void setSnaphotMetadataIfMissing(
+ @NotNull String bucketIdParam,
+ @NotNull String flowIdParam,
+ @NotNull VersionedFlowSnapshot flowSnapshot) {
+
+ VersionedFlowSnapshotMetadata metadata = flowSnapshot.getSnapshotMetadata();
+ if (metadata == null) {
+ metadata = new VersionedFlowSnapshotMetadata();
+ }
+
+ if (metadata.getBucketIdentifier() == null) {
+ metadata.setBucketIdentifier(bucketIdParam);
+ }
+
+ if (metadata.getFlowIdentifier() == null) {
+ metadata.setFlowIdentifier(flowIdParam);
+ }
+
+ flowSnapshot.setSnapshotMetadata(metadata);
+ }
}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java
index 9a9025a..8c8c493 100644
--- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java
@@ -22,7 +22,12 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.Authorizer;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.authorization.resource.Authorizable;
+import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.service.AuthorizationService;
import org.apache.nifi.registry.service.RegistryService;
import org.apache.nifi.registry.service.params.QueryParameters;
import org.apache.nifi.registry.service.params.SortParameter;
@@ -33,6 +38,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
@@ -57,7 +63,7 @@ import java.util.Set;
description = "Create named buckets in the registry to store NiFI objects such flows and extensions. " +
"Search for and retrieve existing buckets."
)
-public class BucketResource {
+public class BucketResource extends AuthorizableApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(BucketResource.class);
@@ -69,7 +75,12 @@ public class BucketResource {
private final RegistryService registryService;
@Autowired
- public BucketResource(final RegistryService registryService, final LinkService linkService) {
+ public BucketResource(
+ final RegistryService registryService,
+ final LinkService linkService,
+ final AuthorizationService authorizationService,
+ final Authorizer authorizer) {
+ super(authorizer, authorizationService);
this.registryService = registryService;
this.linkService = linkService;
}
@@ -81,7 +92,12 @@ public class BucketResource {
value = "Create a named bucket capable of storing NiFi bucket objects such as flows and extension bundles.",
response = Bucket.class
)
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403) })
public Response createBucket(final Bucket bucket) {
+ authorizeAccess(RequestAction.WRITE);
final Bucket createdBucket = registryService.createBucket(bucket);
return Response.status(Response.Status.OK).entity(createdBucket).build();
}
@@ -95,10 +111,15 @@ public class BucketResource {
response = Bucket.class,
responseContainer = "List"
)
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403) })
public Response getBuckets(
@ApiParam(value = SortParameter.API_PARAM_DESCRIPTION, format = "field:order", allowMultiple = true, example = "name:ASC")
@QueryParam("sort")
final List<String> sortParameters) {
+ authorizeAccess(RequestAction.READ);
final QueryParameters.Builder paramsBuilder = new QueryParameters.Builder();
for (String sortParam : sortParameters) {
@@ -120,13 +141,13 @@ public class BucketResource {
"with the set of items in the bucket, but any further children of those items will not be included.",
response = Bucket.class
)
- @ApiResponses(
- value = {
- @ApiResponse(code = 404, message = "The specified resource could not be found."),
- }
- )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404) })
public Response getBucket(@PathParam("bucketId") final String bucketId,
@QueryParam("verbose") @DefaultValue("false") boolean verbose) {
+ authorizeBucketAccess(RequestAction.READ, bucketId);
final Bucket bucket = registryService.getBucket(bucketId, verbose);
linkService.populateBucketLinks(bucket);
@@ -145,28 +166,29 @@ public class BucketResource {
value = "Update the metadata for an existing bucket in the registry. Objects stored in the bucket will not be modified.",
response = Bucket.class
)
- @ApiResponses(
- value = {
- @ApiResponse(code = 404, message = "The specified resource could not be found."),
- }
- )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
public Response updateBucket(@PathParam("bucketId") final String bucketId, final Bucket bucket) {
if (StringUtils.isBlank(bucketId)) {
- throw new IllegalArgumentException("Bucket Id cannot be blank");
+ throw new BadRequestException("Bucket id cannot be blank");
}
if (bucket == null) {
- throw new IllegalArgumentException("Bucket cannot be null");
+ throw new BadRequestException("Bucket cannot be null");
}
if (bucket.getIdentifier() != null && !bucketId.equals(bucket.getIdentifier())) {
- throw new IllegalArgumentException("Bucket id in path param must match bucket id in body");
- }
-
- if (bucket.getIdentifier() == null) {
+ throw new BadRequestException("Bucket id in path param must match bucket id in body");
+ } else {
bucket.setIdentifier(bucketId);
}
+ authorizeBucketAccess(RequestAction.WRITE, bucketId);
+
final Bucket updatedBucket = registryService.updateBucket(bucket);
return Response.status(Response.Status.OK).entity(updatedBucket).build();
}
@@ -179,12 +201,16 @@ public class BucketResource {
value = "Delete an existing bucket in the registry, along with all the objects it is storing.",
response = Bucket.class
)
- @ApiResponses(
- value = {
- @ApiResponse(code = 404, message = "The specified resource could not be found."),
- }
- )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404) })
public Response deleteBucket(@PathParam("bucketId") final String bucketId) {
+ if (StringUtils.isBlank(bucketId)) {
+ throw new BadRequestException("Bucket id cannot be blank");
+ }
+ authorizeBucketAccess(RequestAction.WRITE, bucketId);
final Bucket deletedBucket = registryService.deleteBucket(bucketId);
return Response.status(Response.Status.OK).entity(deletedBucket).build();
}
@@ -203,4 +229,11 @@ public class BucketResource {
return Response.status(Response.Status.OK).entity(fieldsEntity).build();
}
+ private void authorizeAccess(RequestAction actionType) {
+ authorizationService.authorizeAccess(lookup -> {
+ final Authorizable bucketsAuthorizable = lookup.getBucketsAuthorizable();
+ bucketsAuthorizable.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser());
+ });
+ }
+
}