http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Group.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Group.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Group.java new file mode 100644 index 0000000..f22dd97 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Group.java @@ -0,0 +1,263 @@ +/* + * 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.authorization; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * A group that users can belong to. + */ +public class Group { + + private final String identifier; + + private final String name; + + private final Set<String> users; + + private Group(final Builder builder) { + this.identifier = builder.identifier; + this.name = builder.name; + this.users = Collections.unmodifiableSet(new HashSet<>(builder.users)); + + if (this.identifier == null || this.identifier.trim().isEmpty()) { + throw new IllegalArgumentException("Identifier can not be null or empty"); + } + + if (this.name == null || this.name.trim().isEmpty()) { + throw new IllegalArgumentException("Name can not be null or empty"); + } + } + + /** + * @return the identifier of the group + */ + public String getIdentifier() { + return identifier; + } + + /** + * @return the name of the group + */ + public String getName() { + return name; + } + + /** + * @return an unmodifiable set of user identifiers that belong to this group + */ + public Set<String> getUsers() { + return users; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + final Group other = (Group) obj; + return Objects.equals(this.identifier, other.identifier); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.identifier); + } + + @Override + public String toString() { + return String.format("identifier[%s], name[%s]", getIdentifier(), getName()); + } + + + /** + * Builder for creating Groups. + */ + public static class Builder { + + private String identifier; + private String name; + private Set<String> users = new HashSet<>(); + private final boolean fromGroup; + + public Builder() { + this.fromGroup = false; + } + + /** + * Initializes the builder with the state of the provided group. When using this constructor + * the identifier field of the builder can not be changed and will result in an IllegalStateException + * if attempting to do so. + * + * @param other the existing access policy to initialize from + */ + public Builder(final Group other) { + if (other == null) { + throw new IllegalArgumentException("Provided group can not be null"); + } + + this.identifier = other.getIdentifier(); + this.name = other.getName(); + this.users.clear(); + this.users.addAll(other.getUsers()); + this.fromGroup = true; + } + + /** + * Sets the identifier of the builder. + * + * @param identifier the identifier + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group + */ + public Builder identifier(final String identifier) { + if (fromGroup) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing group"); + } + + this.identifier = identifier; + return this; + } + + /** + * Sets the identifier of the builder to a random UUID. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group + */ + public Builder identifierGenerateRandom() { + if (fromGroup) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing group"); + } + + this.identifier = UUID.randomUUID().toString(); + return this; + } + + /** + * Sets the identifier of the builder with a UUID generated from the specified seed string. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group + */ + public Builder identifierGenerateFromSeed(final String seed) { + if (fromGroup) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing group"); + } + if (seed == null) { + throw new IllegalArgumentException("Cannot seed the group identifier with a null value."); + } + + this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString(); + return this; + } + + /** + * Sets the name of the builder. + * + * @param name the name + * @return the builder + */ + public Builder name(final String name) { + this.name = name; + return this; + } + + /** + * Adds all users from the provided set to the builder's set of users. + * + * @param users a set of users to add + * @return the builder + */ + public Builder addUsers(final Set<String> users) { + if (users != null) { + this.users.addAll(users); + } + return this; + } + + /** + * Adds the given user to the builder's set of users. + * + * @param user the user to add + * @return the builder + */ + public Builder addUser(final String user) { + if (user != null) { + this.users.add(user); + } + return this; + } + + /** + * Removes the given user from the builder's set of users. + * + * @param user the user to remove + * @return the builder + */ + public Builder removeUser(final String user) { + if (user != null) { + this.users.remove(user); + } + return this; + } + + /** + * Removes all users from the provided set from the builder's set of users. + * + * @param users the users to remove + * @return the builder + */ + public Builder removeUsers(final Set<String> users) { + if (users != null) { + this.users.removeAll(users); + } + return this; + } + + /** + * Clears the builder's set of users so that users is non-null with size 0. + * + * @return the builder + */ + public Builder clearUsers() { + this.users.clear(); + return this; + } + + /** + * @return a new Group constructed from the state of the builder + */ + public Group build() { + return new Group(this); + } + + } + +}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/ManagedAuthorizer.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/ManagedAuthorizer.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/ManagedAuthorizer.java new file mode 100644 index 0000000..da82f4e --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/ManagedAuthorizer.java @@ -0,0 +1,59 @@ +/* + * 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.authorization; + +import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException; + +public interface ManagedAuthorizer extends Authorizer { + + /** + * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be + * used for comparison to determine if two managed authorizers represent a compatible set of users, + * groups, and/or policies. Must be non null + * + * @return the fingerprint for this Authorizer + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + String getFingerprint() throws AuthorizationAccessException; + + /** + * Parses the fingerprint and adds any users, groups, and policies to the current Authorizer. + * + * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer. + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException; + + /** + * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable. + * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit. + * + * @param proposedFingerprint the proposed fingerprint + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable + */ + void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException; + + /** + * Returns the AccessPolicy provider for this managed Authorizer. Must be non null + * + * @return the AccessPolicy provider + */ + AccessPolicyProvider getAccessPolicyProvider(); + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java new file mode 100644 index 0000000..231b9eb --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java @@ -0,0 +1,54 @@ +/* + * 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.authorization; + +import java.util.StringJoiner; + +/** + * Actions a user/entity can take on a resource. + */ +public enum RequestAction { + READ("read"), + WRITE("write"); + // TODO, add DELETE RequestAction feature + + private String value; + + RequestAction(String value) { + this.value = value; + } + + @Override + public String toString() { + return value.toLowerCase(); + } + + public static RequestAction valueOfValue(final String action) { + if (RequestAction.READ.toString().equals(action)) { + return RequestAction.READ; + } else if (RequestAction.WRITE.toString().equals(action)) { + return RequestAction.WRITE; + } else { + StringJoiner stringJoiner = new StringJoiner(", "); + for(RequestAction ra : RequestAction.values()) { + stringJoiner.add(ra.toString()); + } + String allowableValues = stringJoiner.toString(); + throw new IllegalArgumentException("Action must be one of [" + allowableValues + "]"); + } + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Resource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Resource.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Resource.java new file mode 100644 index 0000000..711f724 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/Resource.java @@ -0,0 +1,44 @@ +/* + * 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.authorization; + +/** + * Resource in an authorization request. + */ +public interface Resource { + + /** + * The identifier for this resource. + * + * @return identifier for this resource + */ + String getIdentifier(); + + /** + * The name of this resource. May be null. + * + * @return name of this resource + */ + String getName(); + + /** + * The description of this resource that may be safely used in messages to the client. + * + * @return safe description + */ + String getSafeDescription(); +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/User.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/User.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/User.java new file mode 100644 index 0000000..79f12a8 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/User.java @@ -0,0 +1,188 @@ +/* + * 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.authorization; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.UUID; + +/** + * A user to create authorization policies for. + */ +public class User { + + private final String identifier; + + private final String identity; + + private User(final Builder builder) { + this.identifier = builder.identifier; + this.identity = builder.identity; + + if (identifier == null || identifier.trim().isEmpty()) { + throw new IllegalArgumentException("Identifier can not be null or empty"); + } + + if (identity == null || identity.trim().isEmpty()) { + throw new IllegalArgumentException("Identity can not be null or empty"); + } + + } + + /** + * @return the identifier of the user + */ + public String getIdentifier() { + return identifier; + } + + /** + * @return the identity string of the user + */ + public String getIdentity() { + return identity; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + final User other = (User) obj; + return Objects.equals(this.identifier, other.identifier); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.identifier); + } + + @Override + public String toString() { + return String.format("identifier[%s], identity[%s]", getIdentifier(), getIdentity()); + } + + /** + * Builder for Users. + */ + public static class Builder { + + private String identifier; + private String identity; + private final boolean fromUser; + + /** + * Default constructor for building a new User. + */ + public Builder() { + this.fromUser = false; + } + + /** + * Initializes the builder with the state of the provided user. When using this constructor + * the identifier field of the builder can not be changed and will result in an IllegalStateException + * if attempting to do so. + * + * @param other the existing user to initialize from + */ + public Builder(final User other) { + if (other == null) { + throw new IllegalArgumentException("Provided user can not be null"); + } + + this.identifier = other.getIdentifier(); + this.identity = other.getIdentity(); + this.fromUser = true; + } + + /** + * Sets the identifier of the builder. + * + * @param identifier the identifier to set + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing User + */ + public Builder identifier(final String identifier) { + if (fromUser) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing user"); + } + + this.identifier = identifier; + return this; + } + + /** + * Sets the identifier of the builder to a random UUID. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing User + */ + public Builder identifierGenerateRandom() { + if (fromUser) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing user"); + } + + this.identifier = UUID.randomUUID().toString(); + return this; + } + + /** + * Sets the identifier of the builder with a UUID generated from the specified seed string. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing User + */ + public Builder identifierGenerateFromSeed(final String seed) { + if (fromUser) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing user"); + } + if (seed == null) { + throw new IllegalArgumentException("Cannot seed the user identifier with a null value."); + } + + this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString(); + return this; + } + + /** + * Sets the identity of the builder. + * + * @param identity the identity to set + * @return the builder + */ + public Builder identity(final String identity) { + this.identity = identity; + return this; + } + + /** + * @return a new User constructed from the state of the builder + */ + public User build() { + return new User(this); + } + + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserAndGroups.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserAndGroups.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserAndGroups.java new file mode 100644 index 0000000..b8f150a --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserAndGroups.java @@ -0,0 +1,40 @@ +/* + * 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.authorization; + +import java.util.Set; + +/** + * A holder object to provide atomic access to a user and their groups. + */ +public interface UserAndGroups { + + /** + * Retrieves the user, or null if the user is unknown + * + * @return the user with the given identity + */ + User getUser(); + + /** + * Retrieves the groups for the user, or null if the user is unknown or has no groups. + * + * @return the set of groups for the given user identity + */ + Set<Group> getGroups(); + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserContextKeys.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserContextKeys.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserContextKeys.java new file mode 100644 index 0000000..daac9e8 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserContextKeys.java @@ -0,0 +1,26 @@ +/* + * 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.authorization; + +/** + * Constants for keys that can be passed in the AuthorizationRequest user context Map. + */ +public enum UserContextKeys { + + CLIENT_ADDRESS; + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProvider.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProvider.java new file mode 100644 index 0000000..c0460da --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProvider.java @@ -0,0 +1,108 @@ +/* + * 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.authorization; + +import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException; + +import java.util.Set; + +/** + * Provides access to Users and Groups. + * + * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to + * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results. + * + * Additionally, extensions need to be thread safe. + */ +public interface UserGroupProvider { + + /** + * Retrieves all users. Must be non null + * + * @return a list of users + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Set<User> getUsers() throws AuthorizationAccessException; + + /** + * Retrieves the user with the given identifier. + * + * @param identifier the id of the user to retrieve + * @return the user with the given id, or null if no matching user was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + User getUser(String identifier) throws AuthorizationAccessException; + + /** + * Retrieves the user with the given identity. + * + * @param identity the identity of the user to retrieve + * @return the user with the given identity, or null if no matching user was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + User getUserByIdentity(String identity) throws AuthorizationAccessException; + + /** + * Retrieves all groups. Must be non null + * + * @return a list of groups + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Set<Group> getGroups() throws AuthorizationAccessException; + + /** + * Retrieves a Group by id. + * + * @param identifier the identifier of the Group to retrieve + * @return the Group with the given identifier, or null if no matching group was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Group getGroup(String identifier) throws AuthorizationAccessException; + + /** + * Gets a user and their groups. Must be non null. If the user is not known the UserAndGroups.getUser() and + * UserAndGroups.getGroups() should return null + * + * @return the UserAndGroups for the specified identity + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException; + + /** + * Called immediately after instance creation for implementers to perform additional setup + * + * @param initializationContext in which to initialize + */ + void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException; + + /** + * Called to configure the Authorizer. + * + * @param configurationContext at the time of configuration + * @throws AuthorizerCreationException for any issues configuring the provider + */ + void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException; + + /** + * Called immediately before instance destruction for implementers to release resources. + * + * @throws AuthorizerDestructionException If pre-destruction fails. + */ + void preDestruction() throws AuthorizerDestructionException; +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderInitializationContext.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderInitializationContext.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderInitializationContext.java new file mode 100644 index 0000000..6a213fa --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderInitializationContext.java @@ -0,0 +1,37 @@ +/* + * 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.authorization; + +/** + * Initialization content for UserGroupProviders. + */ +public interface UserGroupProviderInitializationContext { + + /** + * The identifier of the UserGroupProvider. + * + * @return The identifier + */ + String getIdentifier(); + + /** + * The lookup for accessing other configured UserGroupProviders. + * + * @return The UserGroupProvider lookup + */ + UserGroupProviderLookup getUserGroupProviderLookup(); +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderLookup.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderLookup.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderLookup.java new file mode 100644 index 0000000..ddf6124 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/UserGroupProviderLookup.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization; + +/** + * + */ +public interface UserGroupProviderLookup { + + /** + * Looks up the UserGroupProvider with the specified identifier + * + * @param identifier The identifier of the UserGroupProvider + * @return The UserGroupProvider + */ + UserGroupProvider getUserGroupProvider(String identifier); +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AccessDeniedException.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AccessDeniedException.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AccessDeniedException.java new file mode 100644 index 0000000..7b09a6e --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AccessDeniedException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.exception; + +/** + * Represents any error that might occur while authorizing user requests. + */ +public class AccessDeniedException extends RuntimeException { + private static final long serialVersionUID = -5683444815269084134L; + + public AccessDeniedException(Throwable cause) { + super(cause); + } + + public AccessDeniedException(String message, Throwable cause) { + super(message, cause); + } + + public AccessDeniedException(String message) { + super(message); + } + + public AccessDeniedException() { + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizationAccessException.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizationAccessException.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizationAccessException.java new file mode 100644 index 0000000..407e182 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizationAccessException.java @@ -0,0 +1,32 @@ +/* + * 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.authorization.exception; + +/** + * Represents the case when an authorization decision could not be made because the Authorizer was unable to access the underlying data store. + */ +public class AuthorizationAccessException extends RuntimeException { + + public AuthorizationAccessException(String message, Throwable cause) { + super(message, cause); + } + + public AuthorizationAccessException(String message) { + super(message); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerCreationException.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerCreationException.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerCreationException.java new file mode 100644 index 0000000..2a7ae36 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerCreationException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.exception; + +/** + * Represents the exceptional case when an Authorizer fails instantiation. + * + */ +public class AuthorizerCreationException extends RuntimeException { + + public AuthorizerCreationException() { + } + + public AuthorizerCreationException(String msg) { + super(msg); + } + + public AuthorizerCreationException(Throwable cause) { + super(cause); + } + + public AuthorizerCreationException(String msg, Throwable cause) { + super(msg, cause); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerDestructionException.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerDestructionException.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerDestructionException.java new file mode 100644 index 0000000..0f4a498 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/AuthorizerDestructionException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.exception; + +/** + * Represents the exceptional case when an Authorizer fails destruction. + * + */ +public class AuthorizerDestructionException extends RuntimeException { + + public AuthorizerDestructionException() { + } + + public AuthorizerDestructionException(String msg) { + super(msg); + } + + public AuthorizerDestructionException(Throwable cause) { + super(cause); + } + + public AuthorizerDestructionException(String msg, Throwable cause) { + super(msg, cause); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/UninheritableAuthorizationsException.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/UninheritableAuthorizationsException.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/UninheritableAuthorizationsException.java new file mode 100644 index 0000000..fe110f7 --- /dev/null +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/exception/UninheritableAuthorizationsException.java @@ -0,0 +1,28 @@ +/* + * 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.authorization.exception; + +/** + * Represents the case when the proposed authorizations are not inheritable. + */ +public class UninheritableAuthorizationsException extends RuntimeException { + + public UninheritableAuthorizationsException(String message) { + super(message); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-registry-security/pom.xml b/nifi-registry-security/pom.xml deleted file mode 100644 index d7711e9..0000000 --- a/nifi-registry-security/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - 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. ---> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.apache.nifi.registry</groupId> - <artifactId>nifi-registry</artifactId> - <version>0.0.1-SNAPSHOT</version> - </parent> - <artifactId>nifi-registry-security</artifactId> - <packaging>jar</packaging> - <build> - <resources> - <resource> - <directory>src/main/xsd</directory> - </resource> - </resources> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>jaxb2-maven-plugin</artifactId> - <executions> - <execution> - <id>xjc</id> - <goals> - <goal>xjc</goal> - </goals> - <configuration> - <packageName>org.apache.nifi.registry.user.generated</packageName> - </configuration> - </execution> - </executions> - <configuration> - <outputDirectory>${project.build.directory}/generated-sources/jaxb</outputDirectory> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-checkstyle-plugin</artifactId> - <configuration> - <excludes>**/user/generated/*.java</excludes> - </configuration> - </plugin> - </plugins> - </build> - <dependencies> - <dependency> - <groupId>org.apache.nifi.registry</groupId> - <artifactId>nifi-registry-properties</artifactId> - <version>0.0.1-SNAPSHOT</version> - </dependency> - <dependency> - <groupId>javax.servlet</groupId> - <artifactId>javax.servlet-api</artifactId> - </dependency> - </dependencies> -</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizationProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizationProvider.java b/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizationProvider.java deleted file mode 100644 index 89be63e..0000000 --- a/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizationProvider.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.security; - -import org.apache.nifi.registry.properties.NiFiRegistryProperties; -import org.apache.nifi.registry.user.generated.User; -import org.apache.nifi.registry.user.generated.Users; -import org.xml.sax.SAXException; - -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class AuthorizationProvider { - - private static final String USERS_XSD = "/users.xsd"; - private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.user.generated"; - private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); - - /** - * Load the JAXBContext. - */ - private static JAXBContext initializeJaxbContext() { - try { - return JAXBContext.newInstance(JAXB_GENERATED_PATH, AuthorizationProvider.class.getClassLoader()); - } catch (JAXBException e) { - throw new RuntimeException("Unable to create JAXBContext."); - } - } - - private final List<String> authorizedUsers; - - public AuthorizationProvider(final NiFiRegistryProperties properties) { - final File usersFile = properties.getAuthorizedUsersFile(); - final List<String> userList = new ArrayList<>(); - - // load the users from the specified file - if (usersFile != null && usersFile.exists()) { - try { - // find the schema - final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - final Schema schema = schemaFactory.newSchema(AuthorizationProvider.class.getResource(USERS_XSD)); - - // attempt to unmarshal - final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); - unmarshaller.setSchema(schema); - final JAXBElement<Users> element = unmarshaller.unmarshal(new StreamSource(usersFile), Users.class); - final Users users = element.getValue(); - - // add each users dn - for (final User user : users.getUser()) { - userList.add(user.getDn()); - } - } catch (SAXException | JAXBException e) { - throw new RuntimeException("Unable to read the authorized useres file: " + e, e); - } - } - - authorizedUsers = Collections.unmodifiableList(userList); - } - - public boolean canAccess(final String dn) { - return authorizedUsers.contains(dn); - } -} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizedUserFilter.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizedUserFilter.java b/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizedUserFilter.java deleted file mode 100644 index 3276a66..0000000 --- a/nifi-registry-security/src/main/java/org/apache/nifi/registry/security/AuthorizedUserFilter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.security; - -import java.io.IOException; -import java.io.PrintWriter; -import java.security.cert.X509Certificate; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AuthorizedUserFilter implements Filter { - - private static final Logger logger = LoggerFactory.getLogger(AuthorizedUserFilter.class); - private final AuthorizationProvider provider; - - public AuthorizedUserFilter(final AuthorizationProvider provider) { - this.provider = provider; - } - - @Override - public void init(FilterConfig fc) throws ServletException { - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - final HttpServletRequest httpServletRequest = (HttpServletRequest) request; - final HttpServletResponse httpServletResponse = (HttpServletResponse) response; - - if (request.isSecure()) { - final String dn = getDn(httpServletRequest); - - // if the user has a certificate, extract the dn and see if they can access - if (dn != null && provider.canAccess(dn)) { - chain.doFilter(request, response); - } else { - // set the response status - httpServletResponse.setContentType("text/plain"); - httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); - - // write the response message - PrintWriter out = httpServletResponse.getWriter(); - out.println("Access is denied."); - - // log the failure - logger.info(String.format(String.format("User <%s> is not authorized.", dn))); - } - } else { - chain.doFilter(request, response); - } - } - - private String getDn(final HttpServletRequest request) { - X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); - if (certs != null && certs.length > 0) { - return certs[0].getSubjectDN().getName().trim(); - } else { - return null; - } - } - - @Override - public void destroy() { - } - -} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security/src/main/xsd/users.xsd ---------------------------------------------------------------------- diff --git a/nifi-registry-security/src/main/xsd/users.xsd b/nifi-registry-security/src/main/xsd/users.xsd deleted file mode 100644 index fd54c12..0000000 --- a/nifi-registry-security/src/main/xsd/users.xsd +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0"?> -<!-- - 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. ---> -<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <!-- user --> - <xs:complexType name="User"> - <xs:attribute name="dn"> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:minLength value="1"/> - <xs:pattern value=".*[^\s].*"/> - </xs:restriction> - </xs:simpleType> - </xs:attribute> - </xs:complexType> - - <!-- users --> - <xs:element name="users"> - <xs:complexType> - <xs:sequence> - <xs:element name="user" type="User" minOccurs="0" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - </xs:element> -</xs:schema> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java ---------------------------------------------------------------------- diff --git a/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java new file mode 100644 index 0000000..21aa9a7 --- /dev/null +++ b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/DataUnit.java @@ -0,0 +1,245 @@ +/* + * 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.regex.Matcher; +import java.util.regex.Pattern; + +public enum DataUnit { + + /** + * Bytes + */ + B { + @Override + public double toB(double value) { + return value; + } + + @Override + public double toKB(double value) { + return value / POWERS[1]; + } + + @Override + public double toMB(double value) { + return value / POWERS[2]; + } + + @Override + public double toGB(double value) { + return value / POWERS[3]; + } + + @Override + public double toTB(double value) { + return value / POWERS[4]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toB(sourceSize); + } + }, + /** + * Kilobytes + */ + KB { + @Override + public double toB(double value) { + return value * POWERS[1]; + } + + @Override + public double toKB(double value) { + return value; + } + + @Override + public double toMB(double value) { + return value / POWERS[1]; + } + + @Override + public double toGB(double value) { + return value / POWERS[2]; + } + + @Override + public double toTB(double value) { + return value / POWERS[3]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toKB(sourceSize); + } + }, + /** + * Megabytes + */ + MB { + @Override + public double toB(double value) { + return value * POWERS[2]; + } + + @Override + public double toKB(double value) { + return value * POWERS[1]; + } + + @Override + public double toMB(double value) { + return value; + } + + @Override + public double toGB(double value) { + return value / POWERS[1]; + } + + @Override + public double toTB(double value) { + return value / POWERS[2]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toMB(sourceSize); + } + }, + /** + * Gigabytes + */ + GB { + @Override + public double toB(double value) { + return value * POWERS[3]; + } + + @Override + public double toKB(double value) { + return value * POWERS[2]; + } + + @Override + public double toMB(double value) { + return value * POWERS[1]; + } + + @Override + public double toGB(double value) { + return value; + } + + @Override + public double toTB(double value) { + return value / POWERS[1]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toGB(sourceSize); + } + }, + /** + * Terabytes + */ + TB { + @Override + public double toB(double value) { + return value * POWERS[4]; + } + + @Override + public double toKB(double value) { + return value * POWERS[3]; + } + + @Override + public double toMB(double value) { + return value * POWERS[2]; + } + + @Override + public double toGB(double value) { + return value * POWERS[1]; + } + + @Override + public double toTB(double value) { + return value; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toTB(sourceSize); + } + }; + + public double convert(final double sourceSize, final DataUnit sourceUnit) { + throw new AbstractMethodError(); + } + + public double toB(double size) { + throw new AbstractMethodError(); + } + + public double toKB(double size) { + throw new AbstractMethodError(); + } + + public double toMB(double size) { + throw new AbstractMethodError(); + } + + public double toGB(double size) { + throw new AbstractMethodError(); + } + + public double toTB(double size) { + throw new AbstractMethodError(); + } + + public static final double[] POWERS = {1, + 1024D, + 1024 * 1024D, + 1024 * 1024 * 1024D, + 1024 * 1024 * 1024 * 1024D}; + + public static final String DATA_SIZE_REGEX = "(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB|TB)"; + public static final Pattern DATA_SIZE_PATTERN = Pattern.compile(DATA_SIZE_REGEX); + + public static Double parseDataSize(final String value, final DataUnit units) { + if (value == null) { + return null; + } + + final Matcher matcher = DATA_SIZE_PATTERN.matcher(value.toUpperCase()); + if (!matcher.find()) { + throw new IllegalArgumentException("Invalid data size: " + value); + } + + final String sizeValue = matcher.group(1); + final String unitValue = matcher.group(2); + + final DataUnit sourceUnit = DataUnit.valueOf(unitValue); + final double size = Double.parseDouble(sizeValue); + return units.convert(size, sourceUnit); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java ---------------------------------------------------------------------- diff --git a/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java index 4c1beed..b7476b9 100644 --- a/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java +++ b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java @@ -16,9 +16,19 @@ */ package org.apache.nifi.registry.util; +import java.io.Closeable; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; @@ -31,8 +41,39 @@ import org.slf4j.Logger; */ public class FileUtils { + public static final long TRANSFER_CHUNK_SIZE_BYTES = 1024 * 1024 * 8; //8 MB chunks public static final long MILLIS_BETWEEN_ATTEMPTS = 50L; + /** + * Closes the given closeable quietly - no logging, no exceptions... + * + * @param closeable the thing to close + */ + public static void closeQuietly(final Closeable closeable) { + if (null != closeable) { + try { + closeable.close(); + } catch (final IOException io) {/*IGNORE*/ + + } + } + } + + /** + * Releases the given lock quietly no logging, no exception + * + * @param lock the lock to release + */ + public static void releaseQuietly(final FileLock lock) { + if (null != lock) { + try { + lock.release(); + } catch (final IOException io) { + /*IGNORE*/ + } + } + } + /* Superseded by renamed class bellow */ @Deprecated public static void ensureDirectoryExistAndCanAccess(final File dir) throws IOException { @@ -68,6 +109,140 @@ public class FileUtils { } /** + * Copies the given source file to the given destination file. The given destination will be overwritten if it already exists. + * + * @param source the file to copy + * @param destination the file to copy to + * @param lockInputFile if true will lock input file during copy; if false will not + * @param lockOutputFile if true will lock output file during copy; if false will not + * @param move if true will perform what is effectively a move operation rather than a pure copy. This allows for potentially highly efficient movement of the file but if not possible this will + * revert to a copy then delete behavior. If false, then the file is copied and the source file is retained. If a true rename/move occurs then no lock is held during that time. + * @param logger if failures occur, they will be logged to this logger if possible. If this logger is null, an IOException will instead be thrown, indicating the problem. + * @return long number of bytes copied + * @throws FileNotFoundException if the source file could not be found + * @throws IOException if unable to read or write the underlying streams + * @throws SecurityException if a security manager denies the needed file operations + */ + public static long copyFile(final File source, final File destination, final boolean lockInputFile, final boolean lockOutputFile, final boolean move, final Logger logger) + throws FileNotFoundException, IOException { + + FileInputStream fis = null; + FileOutputStream fos = null; + FileLock inLock = null; + FileLock outLock = null; + long fileSize = 0L; + if (!source.canRead()) { + throw new IOException("Must at least have read permission"); + + } + if (move && source.renameTo(destination)) { + fileSize = destination.length(); + } else { + try { + fis = new FileInputStream(source); + fos = new FileOutputStream(destination); + final FileChannel in = fis.getChannel(); + final FileChannel out = fos.getChannel(); + if (lockInputFile) { + inLock = in.tryLock(0, Long.MAX_VALUE, true); + if (null == inLock) { + throw new IOException("Unable to obtain shared file lock for: " + source.getAbsolutePath()); + } + } + if (lockOutputFile) { + outLock = out.tryLock(0, Long.MAX_VALUE, false); + if (null == outLock) { + throw new IOException("Unable to obtain exclusive file lock for: " + destination.getAbsolutePath()); + } + } + long bytesWritten = 0; + do { + bytesWritten += out.transferFrom(in, bytesWritten, TRANSFER_CHUNK_SIZE_BYTES); + fileSize = in.size(); + } while (bytesWritten < fileSize); + out.force(false); + FileUtils.closeQuietly(fos); + FileUtils.closeQuietly(fis); + fos = null; + fis = null; + if (move && !FileUtils.deleteFile(source, null, 5)) { + if (logger == null) { + FileUtils.deleteFile(destination, null, 5); + throw new IOException("Could not remove file " + source.getAbsolutePath()); + } else { + logger.warn("Configured to delete source file when renaming/move not successful. However, unable to delete file at: " + source.getAbsolutePath()); + } + } + } finally { + FileUtils.releaseQuietly(inLock); + FileUtils.releaseQuietly(outLock); + FileUtils.closeQuietly(fos); + FileUtils.closeQuietly(fis); + } + } + return fileSize; + } + + /** + * Copies the given source file to the given destination file. The given destination will be overwritten if it already exists. + * + * @param source the file to copy from + * @param destination the file to copy to + * @param lockInputFile if true will lock input file during copy; if false will not + * @param lockOutputFile if true will lock output file during copy; if false will not + * @param logger the logger to use + * @return long number of bytes copied + * @throws FileNotFoundException if the source file could not be found + * @throws IOException if unable to read or write to file + * @throws SecurityException if a security manager denies the needed file operations + */ + public static long copyFile(final File source, final File destination, final boolean lockInputFile, final boolean lockOutputFile, final Logger logger) throws FileNotFoundException, IOException { + return FileUtils.copyFile(source, destination, lockInputFile, lockOutputFile, false, logger); + } + + public static long copyFile(final File source, final OutputStream stream, final boolean closeOutputStream, final boolean lockInputFile) throws FileNotFoundException, IOException { + FileInputStream fis = null; + FileLock inLock = null; + long fileSize = 0L; + try { + fis = new FileInputStream(source); + final FileChannel in = fis.getChannel(); + if (lockInputFile) { + inLock = in.tryLock(0, Long.MAX_VALUE, true); + if (inLock == null) { + throw new IOException("Unable to obtain exclusive file lock for: " + source.getAbsolutePath()); + } + + } + + byte[] buffer = new byte[1 << 18]; //256 KB + int bytesRead = -1; + while ((bytesRead = fis.read(buffer)) != -1) { + stream.write(buffer, 0, bytesRead); + } + in.force(false); + stream.flush(); + fileSize = in.size(); + } finally { + FileUtils.releaseQuietly(inLock); + FileUtils.closeQuietly(fis); + if (closeOutputStream) { + FileUtils.closeQuietly(stream); + } + } + return fileSize; + } + + public static long copyFile(final InputStream stream, final File destination, final boolean closeInputStream, final boolean lockOutputFile) throws FileNotFoundException, IOException { + final Path destPath = destination.toPath(); + final long size = Files.copy(stream, destPath); + if (closeInputStream) { + stream.close(); + } + return size; + } + + /** * Deletes the given file. If the given file exists but could not be deleted * this will be printed as a warning to the given logger * http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java ---------------------------------------------------------------------- diff --git a/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java new file mode 100644 index 0000000..c1e353d --- /dev/null +++ b/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java @@ -0,0 +1,261 @@ +/* + * 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.text.NumberFormat; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FormatUtils { + + private static final String UNION = "|"; + + // for Data Sizes + private static final double BYTES_IN_KILOBYTE = 1024; + private static final double BYTES_IN_MEGABYTE = BYTES_IN_KILOBYTE * 1024; + private static final double BYTES_IN_GIGABYTE = BYTES_IN_MEGABYTE * 1024; + private static final double BYTES_IN_TERABYTE = BYTES_IN_GIGABYTE * 1024; + + // for Time Durations + private static final String NANOS = join(UNION, "ns", "nano", "nanos", "nanosecond", "nanoseconds"); + private static final String MILLIS = join(UNION, "ms", "milli", "millis", "millisecond", "milliseconds"); + private static final String SECS = join(UNION, "s", "sec", "secs", "second", "seconds"); + private static final String MINS = join(UNION, "m", "min", "mins", "minute", "minutes"); + private static final String HOURS = join(UNION, "h", "hr", "hrs", "hour", "hours"); + private static final String DAYS = join(UNION, "d", "day", "days"); + private static final String WEEKS = join(UNION, "w", "wk", "wks", "week", "weeks"); + + private static final String VALID_TIME_UNITS = join(UNION, NANOS, MILLIS, SECS, MINS, HOURS, DAYS, WEEKS); + public static final String TIME_DURATION_REGEX = "(\\d+)\\s*(" + VALID_TIME_UNITS + ")"; + public static final Pattern TIME_DURATION_PATTERN = Pattern.compile(TIME_DURATION_REGEX); + + /** + * Formats the specified count by adding commas. + * + * @param count the value to add commas to + * @return the string representation of the given value with commas included + */ + public static String formatCount(final long count) { + return NumberFormat.getIntegerInstance().format(count); + } + + /** + * Formats the specified duration in 'mm:ss.SSS' format. + * + * @param sourceDuration the duration to format + * @param sourceUnit the unit to interpret the duration + * @return representation of the given time data in minutes/seconds + */ + public static String formatMinutesSeconds(final long sourceDuration, final TimeUnit sourceUnit) { + final long millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit); + + final long millisInMinute = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES); + final int minutes = (int) (millis / millisInMinute); + final long secondsMillisLeft = millis - minutes * millisInMinute; + + final long millisInSecond = TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS); + final int seconds = (int) (secondsMillisLeft / millisInSecond); + final long millisLeft = secondsMillisLeft - seconds * millisInSecond; + + return pad2Places(minutes) + ":" + pad2Places(seconds) + "." + pad3Places(millisLeft); + } + + /** + * Formats the specified duration in 'HH:mm:ss.SSS' format. + * + * @param sourceDuration the duration to format + * @param sourceUnit the unit to interpret the duration + * @return representation of the given time data in hours/minutes/seconds + */ + public static String formatHoursMinutesSeconds(final long sourceDuration, final TimeUnit sourceUnit) { + final long millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit); + + final long millisInHour = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); + final int hours = (int) (millis / millisInHour); + final long minutesSecondsMillisLeft = millis - hours * millisInHour; + + return pad2Places(hours) + ":" + formatMinutesSeconds(minutesSecondsMillisLeft, TimeUnit.MILLISECONDS); + } + + private static String pad2Places(final long val) { + return (val < 10) ? "0" + val : String.valueOf(val); + } + + private static String pad3Places(final long val) { + return (val < 100) ? "0" + pad2Places(val) : String.valueOf(val); + } + + /** + * Formats the specified data size in human readable format. + * + * @param dataSize Data size in bytes + * @return Human readable format + */ + public static String formatDataSize(final double dataSize) { + // initialize the formatter + final NumberFormat format = NumberFormat.getNumberInstance(); + format.setMaximumFractionDigits(2); + + // check terabytes + double dataSizeToFormat = dataSize / BYTES_IN_TERABYTE; + if (dataSizeToFormat > 1) { + return format.format(dataSizeToFormat) + " TB"; + } + + // check gigabytes + dataSizeToFormat = dataSize / BYTES_IN_GIGABYTE; + if (dataSizeToFormat > 1) { + return format.format(dataSizeToFormat) + " GB"; + } + + // check megabytes + dataSizeToFormat = dataSize / BYTES_IN_MEGABYTE; + if (dataSizeToFormat > 1) { + return format.format(dataSizeToFormat) + " MB"; + } + + // check kilobytes + dataSizeToFormat = dataSize / BYTES_IN_KILOBYTE; + if (dataSizeToFormat > 1) { + return format.format(dataSizeToFormat) + " KB"; + } + + // default to bytes + return format.format(dataSize) + " bytes"; + } + + public static long getTimeDuration(final String value, final TimeUnit desiredUnit) { + final Matcher matcher = TIME_DURATION_PATTERN.matcher(value.toLowerCase()); + if (!matcher.matches()) { + throw new IllegalArgumentException("Value '" + value + "' is not a valid Time Duration"); + } + + final String duration = matcher.group(1); + final String units = matcher.group(2); + TimeUnit specifiedTimeUnit = null; + switch (units.toLowerCase()) { + case "ns": + case "nano": + case "nanos": + case "nanoseconds": + specifiedTimeUnit = TimeUnit.NANOSECONDS; + break; + case "ms": + case "milli": + case "millis": + case "milliseconds": + specifiedTimeUnit = TimeUnit.MILLISECONDS; + break; + case "s": + case "sec": + case "secs": + case "second": + case "seconds": + specifiedTimeUnit = TimeUnit.SECONDS; + break; + case "m": + case "min": + case "mins": + case "minute": + case "minutes": + specifiedTimeUnit = TimeUnit.MINUTES; + break; + case "h": + case "hr": + case "hrs": + case "hour": + case "hours": + specifiedTimeUnit = TimeUnit.HOURS; + break; + case "d": + case "day": + case "days": + specifiedTimeUnit = TimeUnit.DAYS; + break; + case "w": + case "wk": + case "wks": + case "week": + case "weeks": + final long durationVal = Long.parseLong(duration); + return desiredUnit.convert(durationVal, TimeUnit.DAYS)*7; + } + + final long durationVal = Long.parseLong(duration); + return desiredUnit.convert(durationVal, specifiedTimeUnit); + } + + public static String formatUtilization(final double utilization) { + return utilization + "%"; + } + + private static String join(final String delimiter, final String... values) { + if (values.length == 0) { + return ""; + } else if (values.length == 1) { + return values[0]; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(values[0]); + for (int i = 1; i < values.length; i++) { + sb.append(delimiter).append(values[i]); + } + + return sb.toString(); + } + + /** + * Formats nanoseconds in the format: + * 3 seconds, 8 millis, 3 nanos - if includeTotalNanos = false, + * 3 seconds, 8 millis, 3 nanos (3008000003 nanos) - if includeTotalNanos = true + * + * @param nanos the number of nanoseconds to format + * @param includeTotalNanos whether or not to include the total number of nanoseconds in parentheses in the returned value + * @return a human-readable String that is a formatted representation of the given number of nanoseconds. + */ + public static String formatNanos(final long nanos, final boolean includeTotalNanos) { + final StringBuilder sb = new StringBuilder(); + + final long seconds = nanos > 1000000000L ? nanos / 1000000000L : 0L; + long millis = nanos > 1000000L ? nanos / 1000000L : 0L; + final long nanosLeft = nanos % 1000000L; + + if (seconds > 0) { + sb.append(seconds).append(" seconds"); + } + if (millis > 0) { + if (seconds > 0) { + sb.append(", "); + millis -= seconds * 1000L; + } + + sb.append(millis).append(" millis"); + } + if (seconds > 0 || millis > 0) { + sb.append(", "); + } + sb.append(nanosLeft).append(" nanos"); + + if (includeTotalNanos) { + sb.append(" (").append(nanos).append(" nanos)"); + } + + return sb.toString(); + } +}
