blob: 1921f4bb931a0031c1acc467a9d56499c5f73909 [file] [log] [blame]
/*
* 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.accumulo.core.security;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.accumulo.core.data.ArrayByteSequence;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.util.ByteBufferUtil;
/**
* A collection of authorization strings.
*/
public class Authorizations implements Iterable<byte[]>, Serializable, AuthorizationContainer {
private static final long serialVersionUID = 1L;
private Set<ByteSequence> auths = new HashSet<>();
private List<byte[]> authsList = new ArrayList<>(); // sorted order
/**
* An empty set of authorizations.
*/
public static final Authorizations EMPTY = new Authorizations();
private static final boolean[] validAuthChars = new boolean[256];
/**
* A special header string used when serializing instances of this class.
*
* @see #serialize()
*/
public static final String HEADER = "!AUTH1:";
static {
for (int i = 0; i < 256; i++) {
validAuthChars[i] = false;
}
for (int i = 'a'; i <= 'z'; i++) {
validAuthChars[i] = true;
}
for (int i = 'A'; i <= 'Z'; i++) {
validAuthChars[i] = true;
}
for (int i = '0'; i <= '9'; i++) {
validAuthChars[i] = true;
}
validAuthChars['_'] = true;
validAuthChars['-'] = true;
validAuthChars[':'] = true;
validAuthChars['.'] = true;
validAuthChars['/'] = true;
}
static final boolean isValidAuthChar(byte b) {
return validAuthChars[0xff & b];
}
private void checkAuths() {
Set<ByteSequence> sortedAuths = new TreeSet<>(auths);
for (ByteSequence bs : sortedAuths) {
if (bs.length() == 0) {
throw new IllegalArgumentException("Empty authorization");
}
authsList.add(bs.toArray());
}
}
/**
* Constructs an authorization object from a collection of string authorizations that have each
* already been encoded as UTF-8 bytes. Warning: This method does not verify that each encoded
* string is valid UTF-8.
*
* @param authorizations
* collection of authorizations, as strings encoded in UTF-8
* @throws IllegalArgumentException
* if authorizations is null
* @see #Authorizations(String...)
*/
public Authorizations(Collection<byte[]> authorizations) {
checkArgument(authorizations != null, "authorizations is null");
for (byte[] auth : authorizations)
auths.add(new ArrayByteSequence(auth));
checkAuths();
}
/**
* Constructs an authorization object from a list of string authorizations that have each already
* been encoded as UTF-8 bytes. Warning: This method does not verify that each encoded string is
* valid UTF-8.
*
* @param authorizations
* list of authorizations, as strings encoded in UTF-8 and placed in buffers
* @throws IllegalArgumentException
* if authorizations is null
* @see #Authorizations(String...)
*/
public Authorizations(List<ByteBuffer> authorizations) {
checkArgument(authorizations != null, "authorizations is null");
for (ByteBuffer buffer : authorizations) {
auths.add(new ArrayByteSequence(ByteBufferUtil.toBytes(buffer)));
}
checkAuths();
}
/**
* Constructs an authorizations object from a serialized form. This is NOT a constructor for a set
* of authorizations of size one. Warning: This method does not verify that the encoded serialized
* form is valid UTF-8.
*
* @param authorizations
* a serialized authorizations string produced by {@link #getAuthorizationsArray()} or
* {@link #serialize()}, converted to UTF-8 bytes
* @throws IllegalArgumentException
* if authorizations is null
*/
public Authorizations(byte[] authorizations) {
checkArgument(authorizations != null, "authorizations is null");
String authsString = new String(authorizations, UTF_8);
if (authsString.startsWith(HEADER)) {
// it's the new format
authsString = authsString.substring(HEADER.length());
if (authsString.length() > 0) {
for (String encAuth : authsString.split(",")) {
byte[] auth = Base64.getDecoder().decode(encAuth);
auths.add(new ArrayByteSequence(auth));
}
checkAuths();
}
} else {
// it's the old format
if (authorizations.length > 0)
setAuthorizations(authsString.split(","));
}
}
/**
* Constructs an empty set of authorizations.
*
* @see #Authorizations(String...)
*/
public Authorizations() {}
/**
* Constructs an authorizations object from a set of human-readable authorizations.
*
* @param authorizations
* array of authorizations
* @throws IllegalArgumentException
* if authorizations is null
*/
public Authorizations(String... authorizations) {
setAuthorizations(authorizations);
}
private void setAuthorizations(String... authorizations) {
checkArgument(authorizations != null, "authorizations is null");
auths.clear();
for (String str : authorizations) {
str = str.trim();
auths.add(new ArrayByteSequence(str.getBytes(UTF_8)));
}
checkAuths();
}
/**
* Returns a serialized form of these authorizations.
*
* @return serialized form of these authorizations, as a string encoded in UTF-8
* @see #serialize()
*/
public byte[] getAuthorizationsArray() {
return serialize().getBytes(UTF_8);
}
/**
* Gets the authorizations in sorted order. The returned list is not modifiable.
*
* @return authorizations, each as a string encoded in UTF-8
* @see #Authorizations(Collection)
*/
public List<byte[]> getAuthorizations() {
ArrayList<byte[]> copy = new ArrayList<>(authsList.size());
for (byte[] auth : authsList) {
byte[] bytes = new byte[auth.length];
System.arraycopy(auth, 0, bytes, 0, auth.length);
copy.add(bytes);
}
return Collections.unmodifiableList(copy);
}
/**
* Gets the authorizations in sorted order. The returned list is not modifiable.
*
* @return authorizations, each as a string encoded in UTF-8 and within a buffer
*/
public List<ByteBuffer> getAuthorizationsBB() {
ArrayList<ByteBuffer> copy = new ArrayList<>(authsList.size());
for (byte[] auth : authsList) {
byte[] bytes = new byte[auth.length];
System.arraycopy(auth, 0, bytes, 0, auth.length);
copy.add(ByteBuffer.wrap(bytes));
}
return Collections.unmodifiableList(copy);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String sep = "";
for (ByteSequence auth : auths) {
sb.append(sep);
sep = ",";
sb.append(new String(auth.toArray(), UTF_8));
}
return sb.toString();
}
/**
* Checks whether this object contains the given authorization.
*
* @param auth
* authorization, as a string encoded in UTF-8
* @return true if authorization is in this collection
*/
public boolean contains(byte[] auth) {
return auths.contains(new ArrayByteSequence(auth));
}
/**
* Checks whether this object contains the given authorization. Warning: This method does not
* verify that the encoded string is valid UTF-8.
*
* @param auth
* authorization, as a string encoded in UTF-8
* @return true if authorization is in this collection
*/
@Override
public boolean contains(ByteSequence auth) {
return auths.contains(auth);
}
/**
* Checks whether this object contains the given authorization.
*
* @param auth
* authorization
* @return true if authorization is in this collection
*/
public boolean contains(String auth) {
return auths.contains(new ArrayByteSequence(auth));
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o instanceof Authorizations) {
Authorizations ao = (Authorizations) o;
return auths.equals(ao.auths);
}
return false;
}
@Override
public int hashCode() {
int result = 0;
for (ByteSequence b : auths)
result += b.hashCode();
return result;
}
/**
* Gets the size of this collection of authorizations.
*
* @return collection size
*/
public int size() {
return auths.size();
}
/**
* Checks if this collection of authorizations is empty.
*
* @return true if this collection contains no authorizations
*/
public boolean isEmpty() {
return auths.isEmpty();
}
@Override
public Iterator<byte[]> iterator() {
return getAuthorizations().iterator();
}
/**
* Returns a serialized form of these authorizations. Convert the returned string to UTF-8 bytes
* to deserialize with {@link #Authorizations(byte[])}.
*
* @return serialized form of authorizations
*/
public String serialize() {
StringBuilder sb = new StringBuilder(HEADER);
String sep = "";
for (byte[] auth : authsList) {
sb.append(sep);
sep = ",";
sb.append(Base64.getEncoder().encodeToString(auth));
}
return sb.toString();
}
}