/*
* Licensed to Cinchapi Inc, under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership. Cinchapi Inc. 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 com.cinchapi.concourse.security;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.Map;
import com.cinchapi.concourse.annotate.Restricted;
import com.cinchapi.concourse.server.io.Byteable;
import com.cinchapi.concourse.server.io.ByteableCollections;
import com.cinchapi.concourse.server.io.FileSystem;
import com.cinchapi.concourse.util.ByteBuffers;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
/**
* The {@link LegacyAccessManager} controls access to the pre-0.5.0
* Concourse server by keeping tracking of valid credentials and
* handling authentication requests. This LegacyAccessManager is used
* to upgrade pre-0.5.0 user credentials to work with {@link AccessManager}.
*
* @author knd
*/
public class LegacyAccessManager {
/**
* Create a LegacyAccessManager that stores its legacy
* credentials in {@code backingStore}.
*
* @param backingStore
* @return the LegacyAccessManager
*/
public static LegacyAccessManager create(String backingStore) {
return new LegacyAccessManager(backingStore);
}
// The legacy credentials are stored in memory
private final Map<String, Credentials> credentials = Maps
.newLinkedHashMap();
/**
* Construct a new instance.
*
* @param backingStore
*/
private LegacyAccessManager(String backingStore) {
Iterator<ByteBuffer> it = ByteableCollections.iterator(FileSystem
.readBytes(backingStore));
while (it.hasNext()) {
LegacyAccessManager.Credentials creds = Credentials
.fromByteBuffer(it.next());
credentials.put(creds.getUsername(), creds);
}
}
/**
* Transfer the legacy {@link #credentials} managed by
* this LegacyAccessManager to the specified {@code AccessManager} so that
* they are now working with and managed by {@code AccessManager}.
*
* @param manager
*/
public void transferCredentials(AccessManager manager) {
for (LegacyAccessManager.Credentials creds : credentials.values()) {
manager.insert(ByteBuffers.decodeFromHex(creds.getUsername()),
ByteBuffers.decodeFromHex(creds.getPassword()),
creds.getSalt());
}
}
/**
* Create a user with {@code username} and {@code password} as
* a legacy {@link Credentials} managed by this LegacyAccessManager.
* This method should only be used for testing.
*
* @param username
* @param password
*/
@Restricted
protected void createUser(ByteBuffer username, ByteBuffer password) { // visible
// for
// testing
ByteBuffer salt = Passwords.getSalt();
password = Passwords.hash(password, salt);
Credentials creds = LegacyAccessManager.Credentials.create(
ByteBuffers.encodeAsHex(username),
ByteBuffers.encodeAsHex(password),
ByteBuffers.encodeAsHex(salt));
credentials.put(creds.getUsername(), creds);
}
/**
* Sync the memory store {@link #credentials} to disk
* at {@code backingStore}. This method should only be used
* for testing.
*
* @param backingStore
*/
@Restricted
protected void diskSync(String backingStore) { // visible
// for
// testing
FileChannel channel = FileSystem.getFileChannel(backingStore);
ByteBuffer bytes = ByteableCollections.toByteBuffer(credentials
.values());
try {
channel.write(bytes);
}
catch (IOException e) {
throw Throwables.propagate(e);
}
finally {
FileSystem.closeFileChannel(channel);
}
}
/**
* A grouping of a username, password and salt that together identify a
* valid authentication scheme for a user.
*
* @author knd
*/
private static final class Credentials implements Byteable {
/**
* Create a new set of Credentials for {@code username},
* {@code password} hashed with {@code salt}.
*
* @param username
* @param password
* @param salt
* @return the Credentials
*/
public static Credentials create(String username, String password,
String salt) {
return new Credentials(username, password, salt);
}
/**
* Deserialize the Credentials that are encoded in {@code bytes}.
*
* @param bytes
* @return the Credentials
*/
public static Credentials fromByteBuffer(ByteBuffer bytes) {
String password = ByteBuffers.encodeAsHex(ByteBuffers.get(bytes,
Passwords.PASSWORD_LENGTH));
String salt = ByteBuffers.encodeAsHex(ByteBuffers.get(bytes,
Passwords.SALT_LENGTH));
String username = ByteBuffers.encodeAsHex(ByteBuffers.get(bytes,
bytes.remaining()));
return new Credentials(username, password, salt);
}
private final String password;
private final String salt;
// These are hex encoded values. It Is okay to keep them in memory as a
// strings since the actual password can't be reconstructed from the
// string hash.
private final String username;
/**
* Construct a new instance.
*
* @param username
* @param password
* @param salt
*/
private Credentials(String username, String password, String salt) {
this.username = username;
this.password = password;
this.salt = salt;
}
@Override
public ByteBuffer getBytes() {
ByteBuffer bytes = ByteBuffer.allocate(size());
copyTo(bytes);
bytes.rewind();
return bytes;
}
/**
* Return the hex encoded password.
*
* @return the password hex
*/
public String getPassword() {
return password;
}
/**
* Return the salt as a ByteBuffer.
*
* @return the salt bytes
*/
public ByteBuffer getSalt() {
return ByteBuffers.decodeFromHex(salt);
}
/**
* Return the hex encoded username.
*
* @return the username hex
*/
public String getUsername() {
return username;
}
@Override
public int size() {
return Passwords.PASSWORD_LENGTH + Passwords.SALT_LENGTH
+ ByteBuffers.decodeFromHex(username).capacity();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("username: " + username).append(
System.getProperty("line.separator"));
sb.append("password: " + password).append(
System.getProperty("line.separator"));
sb.append("salt: " + salt).append(
System.getProperty("line.separator"));
return sb.toString();
}
@Override
public void copyTo(ByteBuffer buffer) {
buffer.put(ByteBuffers.decodeFromHex(password));
buffer.put(ByteBuffers.decodeFromHex(salt));
buffer.put(ByteBuffers.decodeFromHex(username));
}
}
}