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());
+        });
+    }
+
 }

Reply via email to