de.schlichtherle.truezip.key.PromptingKeyProvider Maven / Gradle / Ivy
Show all versions of truezip-driver-zip Show documentation
/*
* Copyright (C) 2005-2013 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package de.schlichtherle.truezip.key;
import javax.annotation.CheckForNull;
import java.io.Closeable;
import java.net.URI;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;
/**
* A "safe" key provider which prompts the user for a key for its protected
* resource.
* The user is prompted via an instance of the {@link View} interface.
* The view may then display the resource URI by calling {@link #getResource}
* on this instance and set the key by using the given {@link Controller}.
*
* @param The type of the keys.
* @see PromptingKeyManager
* @author Christian Schlichtherle
*/
@ThreadSafe
public final class PromptingKeyProvider>
extends SafeKeyProvider {
private final View view;
private volatile State state = State.RESET;
/** The resource identifier for the protected resource. */
private volatile @CheckForNull URI resource;
private volatile boolean changeRequested;
private volatile @CheckForNull CacheableUnknownKeyException exception;
PromptingKeyProvider(PromptingKeyManager manager) {
this.view = manager.getView();
}
private View getView() {
return view;
}
private State getState() {
return state;
}
private void setState(final State state) {
assert null != state;
this.state = state;
}
/**
* Returns the unique resource identifier (resource ID) of the protected
* resource for which this key provider is used.
* May be {@code null}.
*/
public @CheckForNull URI getResource() {
return resource;
}
/**
* Returns the unique resource identifier (resource ID) of the protected
* resource for which this key provider is used.
* May be {@code null}.
*/
void setResource(final @CheckForNull URI resource) {
this.resource = resource;
}
@Override
protected void retrieveWriteKey() throws UnknownKeyException {
getState().retrieveWriteKey(this);
}
@Override
protected void retrieveReadKey(boolean invalid)
throws UnknownKeyException {
getState().retrieveReadKey(this, invalid);
}
@Override
protected K getKey() {
return getState().getKey(this);
}
private @CheckForNull K getKey0() {
return super.getKey();
}
@Override
public void setKey(final @CheckForNull K key) {
getState().setKey(this, key);
}
private void setKey0(final @CheckForNull K key) {
super.setKey(key);
}
/**
* Returns whether or not the user shall get prompted for a new key upon
* the next call to {@link #getWriteKey()}, provided that the key
* has been {@link #setKey set} before.
*
* @return Whether or not the user shall get prompted for a new key upon
* the next call to {@link #getWriteKey()}, provided that the key
* has been {@link #setKey set} before.
*/
private boolean isChangeRequested() {
return changeRequested;
}
private void setChangeRequested(final boolean changeRequested) {
this.changeRequested = changeRequested;
}
private @CheckForNull CacheableUnknownKeyException getException() {
return exception;
}
private void setException(
final @CheckForNull CacheableUnknownKeyException exception) {
this.exception = exception;
}
/**
* Resets the state of this key provider, its current key and the value of
* its {@code changeRequested} property
* if and only if prompting for a key has been cancelled.
*/
public void resetCancelledKey() {
getState().resetCancelledKey(this);
}
/**
* Resets the state of this key provider, its current key and the value of
* its {@code changeRequested} property
* unconditionally.
*/
public void resetUnconditionally() {
reset();
}
private void reset() {
setKey0(null);
setChangeRequested(false);
setException(null);
setState(State.RESET);
}
/** Implements the behavior strategy of its enclosing class. */
@ThreadSafe
private enum State {
RESET {
@Override
> void
retrieveWriteKey(final PromptingKeyProvider provider)
throws UnknownKeyException {
State state;
try {
PromptingKeyProvider.BaseController controller
= provider.new WriteController(this);
try {
provider.getView().promptWriteKey(controller);
} finally {
controller.close();
}
} finally {
if (this == (state = provider.getState()))
provider.setState(state = CANCELLED);
}
state.retrieveWriteKey(provider);
}
@Override
> void
retrieveReadKey(PromptingKeyProvider provider, boolean invalid)
throws UnknownKeyException {
State state;
do {
try {
PromptingKeyProvider.BaseController controller
= provider.new ReadController(this);
try {
provider.getView().promptReadKey(controller, invalid);
} finally {
controller.close();
}
} catch (CacheableUnknownKeyException ex) {
setException(provider, ex);
}
state = provider.getState();
} while (state == this);
state.retrieveReadKey(provider, false);
}
@Override
> void
resetCancelledKey(PromptingKeyProvider provider) {
}
},
SET {
@Override
> void
retrieveWriteKey(PromptingKeyProvider provider)
throws UnknownKeyException {
if (provider.isChangeRequested()) {
provider.setChangeRequested(false);
RESET.retrieveWriteKey(provider); // DON'T change state!
}
}
@Override
> void
retrieveReadKey(PromptingKeyProvider provider, boolean invalid)
throws UnknownKeyException {
if (invalid) {
provider.setState(RESET);
RESET.retrieveReadKey(provider, true);
}
}
@Override
> void
resetCancelledKey(PromptingKeyProvider provider) {
}
},
CANCELLED {
@Override
> void
retrieveWriteKey(PromptingKeyProvider provider)
throws UnknownKeyException {
throw getException(provider);
}
@Override
> void
retrieveReadKey(PromptingKeyProvider provider, boolean invalid)
throws UnknownKeyException {
throw getException(provider);
}
@Override
> void
resetCancelledKey(PromptingKeyProvider provider) {
provider.reset();
}
};
abstract > void
retrieveWriteKey(PromptingKeyProvider provider)
throws UnknownKeyException;
abstract > void
retrieveReadKey(PromptingKeyProvider provider, boolean invalid)
throws UnknownKeyException;
abstract > void
resetCancelledKey(PromptingKeyProvider provider);
final @CheckForNull > K
getKey(PromptingKeyProvider provider) {
return provider.getKey0();
}
final > void
setKey(PromptingKeyProvider provider, @CheckForNull K key) {
provider.setKey0(key);
provider.setState(null != key ? State.SET : State.CANCELLED);
}
> void
setChangeRequested(PromptingKeyProvider provider, boolean changeRequested) {
provider.setChangeRequested(changeRequested);
}
@CheckForNull > URI
getResource(PromptingKeyProvider provider) {
return provider.getResource();
}
final > CacheableUnknownKeyException
getException(PromptingKeyProvider provider) {
CacheableUnknownKeyException ex = provider.getException();
if (null == ex)
provider.setException(ex = new KeyPromptingCancelledException());
return ex;
}
final > void
setException(PromptingKeyProvider provider, CacheableUnknownKeyException ex) {
provider.setException(ex);
provider.setState(CANCELLED);
}
} // State
/**
* Used for the actual prompting of the user for a key (a password for
* example) which is required to access a protected resource.
* This interface is not depending on any particular techology, so
* prompting could be implemented using Swing, the console, a web page or
* no user interface technology at all.
*
* Implementations of this interface are maintained by a
* {@link PromptingKeyManager}.
*
* Implementations of this interface must be thread safe
* and should have no side effects!
*/
public interface View> {
/**
* Prompts the user for the key for (over)writing the contents of a
* new or existing protected resource.
* Upon return, the implementation should have updated the
* {@link Controller#setKey key} property of the given
* {@code controller}.
*
* If the implementation has called {@link Controller#setKey} with a
* non-{@code null} parameter, then a clone of this object will be
* used as the key.
*
* Otherwise, prompting for a key is permanently disabled and each
* subsequent call to {@link #getWriteKey} or {@link #getReadKey}
* results in a {@link KeyPromptingCancelledException} until
* {@link #resetCancelledKey()} or {@link #resetUnconditionally()} gets
* called.
*
* @param controller The key controller for storing the result.
* @throws UnknownKeyException if key prompting fails for any reason.
*/
void promptWriteKey(Controller controller)
throws UnknownKeyException;
/**
* Prompts the user for the key for reading the contents of an
* existing protected resource.
* Upon return, the implementation should have updated the
* {@link Controller#setKey key} property of the given
* {@code controller}.
*
* If the implementation has called {@link Controller#setKey} with a
* non-{@code null} parameter, then a clone of this object will be
* used as the key.
*
* Otherwise, if the implementation has called {@link Controller#setKey}
* with a {@code null} parameter or throws a
* {@link KeyPromptingCancelledException}, then prompting for the key
* is permanently disabled and each subsequent call to
* {@link #getWriteKey} or {@link #getReadKey} results in a
* {@link KeyPromptingCancelledException} until
* {@link #resetCancelledKey()} or {@link #resetUnconditionally()} gets
* called.
*
* Otherwise, the state of the key provider is not changed and this
* method gets called again.
*
* @param controller The key controller for storing the result.
* @param invalid {@code true} iff a previous call to this method
* resulted in an invalid key.
* @throws KeyPromptingCancelledException if key prompting has been
* cancelled by the user.
* @throws UnknownKeyException if key prompting fails for any other
* reason.
*/
void promptReadKey(Controller controller, boolean invalid)
throws UnknownKeyException;
} // View
/** Proxies access to the key for {@link View} implementations. */
@NotThreadSafe
public interface Controller> {
/**
* Returns the unique resource identifier (resource ID) of the
* protected resource for which this controller is used.
*
* @throws IllegalStateException if getting this property is not legal
* in the current state.
*/
URI getResource();
/**
* Returns the protected resource's key.
*
* @return The protected resource's key.
* @throws IllegalStateException if getting key is not legal in the
* current state.
*/
@CheckForNull K getKey();
/**
* Sets the protected resource's key to a clone of the given key.
*
* @param key The key to clone to use for the protected resource.
* @throws IllegalStateException if setting key is not legal in the
* current state.
*/
void setKey(@CheckForNull K key);
/**
* Requests to prompt the user for a new key upon the next call to
* {@link #getWriteKey()}, provided that the key is
* {@link #setKey set} by then.
*
* @param changeRequested whether or not the user shall get prompted
* for a new key upon the next call to {@link #getWriteKey()},
* provided that the key is {@link #setKey set} then.
* @throws IllegalStateException if setting this property is not legal
* in the current state.
*/
void setChangeRequested(boolean changeRequested);
} // Controller
/** Proxies access to the secret key for {@link View} implementations. */
@NotThreadSafe
private abstract class BaseController implements Controller, Closeable {
private @CheckForNull State state;
BaseController(final State state) {
this.state = state;
}
private State getState() {
final State state = this.state;
if (null == state)
throw new IllegalStateException();
return state;
}
@Override
public void close() {
this.state = null;
}
@Override
public URI getResource() {
final URI resource = getState().getResource(PromptingKeyProvider.this);
if (null == resource)
throw new IllegalStateException();
return resource;
}
@Override
public K getKey() {
return getState().getKey(PromptingKeyProvider.this);
}
@Override
public void setKey(K key) {
getState().setKey(PromptingKeyProvider.this, key);
}
@Override
public void setChangeRequested(boolean changeRequested) {
getState().setChangeRequested(PromptingKeyProvider.this, changeRequested);
}
} // BaseController
/**
* The controller to use when promting for a secret key to encrypt a
* protected resource.
*/
@NotThreadSafe
private final class WriteController extends BaseController {
WriteController(State state) {
super(state);
}
@Override
public void setChangeRequested(boolean changeRequested) {
throw new IllegalStateException();
}
} // WriteController
/**
* The controller to use when promting for a secret key to decrypt a
* protected resource.
*/
@NotThreadSafe
private final class ReadController extends BaseController {
ReadController(State state) {
super(state);
}
@Override
public K getKey() {
throw new IllegalStateException();
}
} // ReadController
}