/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed 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 keywhiz.api.model;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import keywhiz.api.ApiDate;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.nullToEmpty;
import static org.apache.commons.lang3.StringUtils.chomp;
import static org.apache.commons.lang3.StringUtils.removeEnd;
/**
* Immutable Secret data model.
*
* Private fields and getters used for immutability with defaults. We don't count on the Constructor
* with arguments due to de-serialization.
*/
public class Secret {
/** Row id of secret series. */
private final long id;
/** Name of secret or the secret series. */
private final String name;
private final String description;
/** Base64-encoded content of this version of the secret. */
private String secret;
private final LazyString encryptedSecret;
private final String checksum;
private final ApiDate createdAt;
private final String createdBy;
private final ApiDate updatedAt;
private final String updatedBy;
/** Key-value metadata of the secret. */
private final ImmutableMap<String, String> metadata;
private final String type;
private final ImmutableMap<String, String> generationOptions;
private final long expiry;
/** Current version of the secret (may be null) */
private final Long version;
public Secret(long id,
String name,
@Nullable String description,
LazyString encryptedSecret,
String checksum,
ApiDate createdAt,
@Nullable String createdBy,
ApiDate updatedAt,
@Nullable String updatedBy,
@Nullable Map<String, String> metadata,
@Nullable String type,
@Nullable Map<String, String> generationOptions,
long expiry,
@Nullable Long version) {
checkArgument(!name.isEmpty());
this.id = id;
this.name = name;
this.description = nullToEmpty(description);
this.encryptedSecret = checkNotNull(encryptedSecret);
this.checksum = checksum;
this.createdAt = checkNotNull(createdAt);
this.createdBy = nullToEmpty(createdBy);
this.updatedAt = checkNotNull(updatedAt);
this.updatedBy = nullToEmpty(updatedBy);
this.metadata = (metadata == null) ?
ImmutableMap.of() : ImmutableMap.copyOf(metadata);
this.type = type;
this.generationOptions = (generationOptions == null) ?
ImmutableMap.of() : ImmutableMap.copyOf(generationOptions);
this.expiry = expiry;
this.version = version;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
/** @return Name to serialize for clients. */
public String getDisplayName() {
return name;
}
public String getDescription() {
return description;
}
public String getSecret() {
if (secret == null) {
secret = checkNotNull(encryptedSecret.decrypt());
}
return secret;
}
public String getChecksum() {
return checksum;
}
public ApiDate getCreatedAt() {
return createdAt;
}
public String getCreatedBy() {
return createdBy;
}
public ApiDate getUpdatedAt() {
return updatedAt;
}
public String getUpdatedBy() {
return updatedBy;
}
public ImmutableMap<String, String> getMetadata() {
return metadata;
}
public Optional<String> getType() {
return Optional.ofNullable(type);
}
public ImmutableMap<String, String> getGenerationOptions() {
return generationOptions;
}
public long getExpiry() {
return expiry;
}
public Optional<Long> getVersion() {return Optional.ofNullable(version); }
/** Slightly hokey way of calculating the decoded-length without bothering to decode. */
public static int decodedLength(String secret) {
checkNotNull(secret);
// Remove newlines and padding from the end of our secret string.
secret = removeEnd(removeEnd(chomp(secret), "="), "=");
return (secret.length() * 3) / 4;
}
@Override
public boolean equals(Object o) {
if (o instanceof Secret) {
Secret that = (Secret) o;
if (Objects.equal(this.id, that.id) &&
Objects.equal(this.name, that.name) &&
Objects.equal(this.description, that.description) &&
Objects.equal(this.getSecret(), that.getSecret()) &&
Objects.equal(this.getChecksum(), that.getChecksum()) &&
Objects.equal(this.createdAt, that.createdAt) &&
Objects.equal(this.createdBy, that.createdBy) &&
Objects.equal(this.updatedAt, that.updatedAt) &&
Objects.equal(this.updatedBy, that.updatedBy) &&
Objects.equal(this.metadata, that.metadata) &&
Objects.equal(this.type, that.type) &&
Objects.equal(this.generationOptions, that.generationOptions) &&
this.expiry == that.expiry &&
Objects.equal(this.version, that.version)) {
return true;
}
}
return false;
}
@Override public int hashCode() {
return Objects.hashCode(id, name, description, getSecret(), checksum, createdAt, createdBy, updatedAt,
updatedBy, metadata, type, generationOptions, expiry);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("name", name)
.add("description", description)
.add("secret", "[REDACTED]")
.add("checksum", checksum)
.add("creationDate", createdAt)
.add("createdBy", createdBy)
.add("updatedDate", updatedAt)
.add("updatedBy", updatedBy)
.add("metadata", metadata)
.add("type", type)
.add("generationOptions", generationOptions)
.add("expiry", expiry)
.add("version", version)
.toString();
}
public interface LazyString {
String decrypt();
}
}