001package org.kuali.common.util.enc; 002 003import java.util.List; 004 005import org.jasypt.util.text.TextEncryptor; 006import org.kuali.common.util.Assert; 007import org.kuali.common.util.PropertyUtils; 008import org.kuali.common.util.Str; 009import org.kuali.common.util.spring.SpringUtils; 010import org.kuali.common.util.spring.env.BasicEnvironmentService; 011import org.kuali.common.util.spring.env.EnvUtils; 012import org.kuali.common.util.spring.env.EnvironmentService; 013 014import com.google.common.base.Optional; 015import com.google.common.collect.ImmutableList; 016 017public final class EncContext { 018 019 private final Optional<TextEncryptor> textEncryptor; 020 private final EncStrength strength; 021 022 private EncContext(Builder builder) { 023 this.strength = builder.strength; 024 this.textEncryptor = builder.textEncryptor; 025 } 026 027 public Optional<TextEncryptor> getTextEncryptor() { 028 return textEncryptor; 029 } 030 031 public EncStrength getStrength() { 032 return strength; 033 } 034 035 public static Builder builder(String password) { 036 return new Builder(password); 037 } 038 039 public static Builder builder(EnvironmentService env) { 040 return new Builder(env); 041 } 042 043 public static class Builder { 044 045 // Required (but optional) 046 private final Optional<String> password; 047 private final Optional<EnvironmentService> env; 048 049 // Optional 050 private Optional<TextEncryptor> textEncryptor = Optional.absent(); 051 private EncStrength strength = EncStrength.BASIC; 052 private boolean required = false; 053 private boolean removeSystemProperties = false; 054 055 private static final List<String> PASSWORD_KEYS = ImmutableList.of("enc.password", "properties.enc.password"); 056 private static final List<String> STRENGTH_KEYS = ImmutableList.of("enc.strength", "properties.enc.strength"); 057 private static final List<String> PASSWORD_REQUIRED_KEYS = ImmutableList.of("enc.password.required", "properties.decrypt"); 058 private static final String PASSWORD_REMOVE_KEY = "enc.password.removeSystemProperty"; 059 060 /** 061 * Setup encryption using <code>password</code> 062 */ 063 public Builder(String password) { 064 this(EnvUtils.ABSENT, Optional.of(password)); 065 } 066 067 /** 068 * Use the password they gave us, unless it is overridden by a password in the environment 069 */ 070 public Builder(EnvironmentService env, String password) { 071 this(Optional.of(env), Optional.of(password)); 072 } 073 074 /** 075 * Use system properties / environment variables to locate the encryption password 076 */ 077 public Builder() { 078 this(new BasicEnvironmentService()); 079 } 080 081 /** 082 * Locate the encryption password in the environment 083 */ 084 public Builder(EnvironmentService env) { 085 this(Optional.of(env), Optional.<String> absent()); 086 } 087 088 private Builder(Optional<EnvironmentService> env, Optional<String> password) { 089 if (env.isPresent()) { 090 this.password = SpringUtils.getString(env, PASSWORD_KEYS, password); 091 } else { 092 this.password = password; 093 } 094 this.env = env; 095 } 096 097 public Builder removeSystemProperties(boolean removeSystemProperties) { 098 this.removeSystemProperties = removeSystemProperties; 099 return this; 100 } 101 102 public Builder required(boolean required) { 103 this.required = required; 104 return this; 105 } 106 107 public Builder strength(EncStrength strength) { 108 this.strength = strength; 109 return this; 110 } 111 112 private void override() { 113 if (env.isPresent()) { 114 strength(SpringUtils.getProperty(env, STRENGTH_KEYS, EncStrength.class, strength)); 115 required(SpringUtils.getProperty(env, PASSWORD_REQUIRED_KEYS, Boolean.class, required)); 116 removeSystemProperties(env.get().getBoolean(PASSWORD_REMOVE_KEY, removeSystemProperties)); 117 } 118 } 119 120 private void validate(EncContext ctx, boolean required, Optional<String> password) { 121 Assert.notNull(ctx.getTextEncryptor(), "'textEncryptor' cannot be null"); 122 Assert.notNull(ctx.getStrength(), "'strength' cannot be null"); 123 if (required) { 124 Assert.isTrue(ctx.getTextEncryptor().isPresent()); 125 } 126 if (password.isPresent()) { 127 Assert.noBlanks(password.get()); 128 Assert.notEncrypted(password.get()); 129 Assert.notConcealed(password.get()); 130 } 131 } 132 133 private void finish() { 134 override(); 135 if (password.isPresent()) { 136 String revealed = Str.reveal(password.get()); 137 TextEncryptor enc = EncUtils.getTextEncryptor(revealed, strength); 138 this.textEncryptor = Optional.of(enc); 139 } 140 } 141 142 public EncContext build() { 143 // Finish setting up the builder 144 finish(); 145 146 // Get local references to builder instance variables 147 boolean required = this.required; 148 boolean removeSystemProperties = this.removeSystemProperties; 149 Optional<String> password = Optional.fromNullable(this.password.orNull()); 150 151 // Construct the encryption context 152 EncContext ctx = new EncContext(this); 153 154 // Validate that it's in good shape 155 validate(ctx, required, password); 156 157 // Now that we've successfully created and validated the instance, it's safe to remove the system properties 158 if (removeSystemProperties) { 159 PropertyUtils.removeSystemProperties(PASSWORD_KEYS); 160 } 161 162 // Return the context 163 return ctx; 164 } 165 166 } 167 168}