mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-04 18:20:14 +00:00
bug5048: integrated CrossDomainStorage with UserService; not tested yet...
This commit is contained in:
1 parent
956709dc77
commit
83ddb13865
22 files changed
+499
-170
No files matched your search
+48
@@ -0,0 +1,48 @@
|
||||
package com.google.gwt.user.client.rpc.core.com.sap.sse.security.ui.shared;
|
||||
|
||||
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
|
||||
import com.google.gwt.user.client.rpc.SerializationException;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamReader;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
|
||||
import com.sap.sse.security.ui.shared.SecurityServiceSharingDTO;
|
||||
|
||||
public class SecurityServiceSharingDTO_CustomFieldSerializer extends CustomFieldSerializer<SecurityServiceSharingDTO> {
|
||||
@Override
|
||||
public boolean hasCustomInstantiateInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityServiceSharingDTO instantiateInstance(SerializationStreamReader streamReader) throws SerializationException {
|
||||
return instantiate(streamReader);
|
||||
}
|
||||
|
||||
public static SecurityServiceSharingDTO instantiate(SerializationStreamReader streamReader) throws SerializationException {
|
||||
final String jSessionIdCookieDomain = streamReader.readString();
|
||||
final String baseUrlForCrossDomainStorage = streamReader.readString();
|
||||
return new SecurityServiceSharingDTO(jSessionIdCookieDomain, baseUrlForCrossDomainStorage);
|
||||
}
|
||||
|
||||
public static void deserialize(SerializationStreamReader streamReader, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
// handled by instantiateInstance
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeInstance(SerializationStreamReader streamReader, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
deserialize(streamReader, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeInstance(SerializationStreamWriter streamWriter, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
serialize(streamWriter, instance);
|
||||
}
|
||||
|
||||
public static void serialize(SerializationStreamWriter streamWriter, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
streamWriter.writeString(instance.getjSessionIdCookieDomain());
|
||||
streamWriter.writeString(instance.getBaseUrlForCrossDomainStorage());
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -35,7 +35,7 @@ public class UserSettingsPresenter<C extends ClientFactoryWithDispatch & ErrorAn
|
||||
|
||||
@Override
|
||||
public void loadData() {
|
||||
if(clientFactory.getAuthenticationManager().getAuthenticationContext().isLoggedIn()) {
|
||||
if (clientFactory.getAuthenticationManager().getAuthenticationContext().isLoggedIn()) {
|
||||
clientFactory.getUserService().getAllPreferences(new AsyncCallback<Map<String,String>>() {
|
||||
@Override
|
||||
public void onSuccess(Map<String, String> result) {
|
||||
@@ -63,7 +63,7 @@ public class UserSettingsPresenter<C extends ClientFactoryWithDispatch & ErrorAn
|
||||
allKeys.addAll(userSettings.keySet());
|
||||
allKeys.addAll(localSettings.keySet());
|
||||
currentlyShownEntries.clear();
|
||||
for(String key : allKeys) {
|
||||
for (String key : allKeys) {
|
||||
currentlyShownEntries.add(new UserSettingsEntry(key, userSettings.get(key), localSettings.get(key)));
|
||||
}
|
||||
this.updateData();
|
||||
@@ -74,9 +74,9 @@ public class UserSettingsPresenter<C extends ClientFactoryWithDispatch & ErrorAn
|
||||
if (Storage.isLocalStorageSupported()) {
|
||||
localSettings = new HashMap<String, String>();
|
||||
Storage localStorage = Storage.getLocalStorageIfSupported();
|
||||
for(int i = 0; i< localStorage.getLength(); i++) {
|
||||
for (int i = 0; i< localStorage.getLength(); i++) {
|
||||
String key = localStorage.key(i);
|
||||
if(key.startsWith(SailingSettingsConstants.USER_SETTINGS_UI)) {
|
||||
if (key.startsWith(SailingSettingsConstants.USER_SETTINGS_UI)) {
|
||||
String value = localStorage.getItem(key);
|
||||
localSettings.put(key, value);
|
||||
}
|
||||
|
||||
+2
-3
@@ -321,11 +321,10 @@ public class CompetitorFilterPanel extends FlowPanel implements KeyUpHandler, Fi
|
||||
|
||||
private void storeCompetitorsFilterSets(CompetitorsFilterSets newCompetitorsFilterSets) {
|
||||
Storage localStorage = Storage.getLocalStorageIfSupported();
|
||||
if(localStorage != null) {
|
||||
if (localStorage != null) {
|
||||
// delete old value
|
||||
localStorage.removeItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY);
|
||||
|
||||
// store the competiors filter set
|
||||
// store the competitors filter set
|
||||
CompetitorsFilterSetsJsonDeSerializer serializer = new CompetitorsFilterSetsJsonDeSerializer();
|
||||
JSONObject jsonObject = serializer.serialize(newCompetitorsFilterSets);
|
||||
localStorage.setItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY, jsonObject.toString());
|
||||
|
||||
@@ -57,6 +57,7 @@ Export-Package: com.google.gwt.user.client.rpc.core.java.util,
|
||||
com.sap.sse.gwt.client.shared.settings,
|
||||
com.sap.sse.gwt.client.suggestion,
|
||||
com.sap.sse.gwt.client.useragent,
|
||||
com.sap.sse.gwt.client.xdstorage,
|
||||
com.sap.sse.gwt.common,
|
||||
com.sap.sse.gwt.dispatch.client,
|
||||
com.sap.sse.gwt.dispatch.client.system,
|
||||
|
||||
@@ -8,7 +8,23 @@ import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.event.shared.HandlerRegistration;
|
||||
|
||||
/**
|
||||
* Represents the browser's localStorage and sessionStorage objects and provides eventing support.
|
||||
* Represents the browser's localStorage and sessionStorage objects and provides eventing support. Other than
|
||||
* {@link com.google.gwt.storage.client.Storage}, here the {@link #registerAsStorageEventHandler() event registration}
|
||||
* works across browser tabs and windows, as it should, whereas for the GWT-provided
|
||||
* {@link com.google.gwt.storage.client.Storage} this only seems to work within a single browser tab for all browsers
|
||||
* classified as "safari" which includes all Chrome versoins, and therefore is useless for our purposes and defeats the
|
||||
* specification of this web browser feature.
|
||||
* <p>
|
||||
*
|
||||
* There is a theoretical possibility to override GWT's replacement rules leading to this strange behavior. See
|
||||
* {@code Storage.gwt.xml}. It replaces the correct native storage eventing implementation by a proprietary one
|
||||
* which then only works inside the same tab, and the replacement rules include, in addition to ancient browser types,
|
||||
* all "safari" browsers, causing the problem. Such replacement rules may be overridable in our own .gwt.xml module
|
||||
* specifications, but in a quick test we were unable to modify the GWT Storage's behavior to suit our needs.<p>
|
||||
*
|
||||
* This class is a "drop-in replacement" for {@link com.google.gwt.storage.client.Storage} together with
|
||||
* {@link StorageEvent} "drop-in replacing" {@link com.google.gwt.storage.client.StorageEvent}, and then have compatible
|
||||
* methods.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.gwt.dom.client.Document;
|
||||
import com.google.gwt.event.shared.HandlerRegistration;
|
||||
import com.sap.sse.gwt.client.Storage;
|
||||
import com.sap.sse.gwt.client.xdstorage.impl.CrossDomainStorageFallingBackToLocalAfterTimeout;
|
||||
import com.sap.sse.gwt.client.xdstorage.impl.LocalStorage;
|
||||
import com.sap.sse.gwt.client.xdstorage.impl.StorageMessagingEntryPoint;
|
||||
|
||||
/**
|
||||
@@ -37,10 +38,20 @@ public interface CrossDomainStorage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #create(Document, String)}, inserting the {@code iframe} into the {@link Document#get() current document}.
|
||||
* If {@code baseUrlForStorageMessagingEntryPoint} is {@code null}, a {@link LocalStorage} will be returned.
|
||||
* Otherwise, the method behaves like {@link #create(Document, String)}, inserts the {@code iframe} into the
|
||||
* {@link Document#get() current document} and returns a {@link CrossDomainStorageFallingBackToLocalAfterTimeout}
|
||||
* which only resorts to a {@link LocalStorage} again in case the iframe doesn't load within an appropriate
|
||||
* timeout starting with the first request.
|
||||
*/
|
||||
static CrossDomainStorage create(String baseUrlForStorageMessagingEntryPoint) {
|
||||
return create(Document.get(), baseUrlForStorageMessagingEntryPoint);
|
||||
final CrossDomainStorage result;
|
||||
if (baseUrlForStorageMessagingEntryPoint == null) {
|
||||
result = new LocalStorage();
|
||||
} else {
|
||||
result = create(Document.get(), baseUrlForStorageMessagingEntryPoint);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HandlerRegistration addStorageEventHandler(final CrossDomainStorageEvent.Handler handler);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.sap.sse.gwt.client.xdstorage;
|
||||
|
||||
import com.sap.sse.gwt.client.Storage;
|
||||
|
||||
/**
|
||||
* A change event sent to a {@link Handler} object that has been {@link Storage#addStorageEventHandler(Handler)
|
||||
* registered} with the browser's {@link Storage}. It will receive events from both, the session storage and the local
|
||||
|
||||
Executable
+186
@@ -0,0 +1,186 @@
|
||||
package com.sap.sse.gwt.client.xdstorage;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.event.shared.HandlerRegistration;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.sap.sse.gwt.client.messaging.MessagePort;
|
||||
import com.sap.sse.gwt.client.xdstorage.CrossDomainStorageEvent.Handler;
|
||||
import com.sap.sse.gwt.client.xdstorage.impl.CrossDomainStorageImpl;
|
||||
|
||||
/**
|
||||
* Exposes the {@link CrossDomainStorage} interface immediately after object creation, but may start out without an
|
||||
* actual implementation objects to which all requests are delegated. Until this delegation target becomes available,
|
||||
* requests are enqueued. They are executed in order once the delegation target is
|
||||
* {@link #setStorageToUse(CrossDomainStorage) set}.
|
||||
* <p>
|
||||
*
|
||||
* Optionally, a timeout can be set, defining how long to wait for the delegation target to be set after the first
|
||||
* {@link CrossDomainStorage} method call has been received, together with a {@link Supplier} for a
|
||||
* {@link CrossDomainStorage} delegation target implementation to default to if the timeout expires. If no such
|
||||
* timeout is set, requests could potentially remain queued forever.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class DelegatingCrossDomainStorageFuture implements CrossDomainStorage {
|
||||
/**
|
||||
* Starts out as {@code null}. It can be set using {@link #setStorageToUse(CrossDomainStorage)} which will then also
|
||||
* process any requests queued.
|
||||
* <p>
|
||||
*
|
||||
* When a request comes in through any of the {@link CrossDomainStorage} methods and this field is still
|
||||
* {@code null} and a timeout has been defined, a timer is started with the {@link #timeoutInMillis}
|
||||
* duration measured in milliseconds, and the request is recorded in the {@link #queuedWhileWaitingForStorage} queue.
|
||||
* When the timer times out, or the storage to use is provided---whichever comes first---the queued requests
|
||||
* will be executed with the now decided {@link CrossDomainStorage} which is then also set to this field. In case
|
||||
* the timer timed out, a default {@link Supplier} will be asked to supply its value which is then stored to
|
||||
* this field and used from that point forward.
|
||||
*/
|
||||
private CrossDomainStorage storageToUse;
|
||||
|
||||
/**
|
||||
* When request methods are called through any of the {@link CrossDomainStorage} interface methods and the
|
||||
* {@link #portToStorageMessagingEntryPoint} is not ready yet (still {@code null}), requests are queued in this list
|
||||
* until the moment when the message port is set after the iframe has announced its readiness, or the timer has timed
|
||||
* out, whichever comes first. Then, the requests are worked down, one after the other, using the {@link #crossDomainStorage}
|
||||
* through the iframe if it became ready in time, or using the {@link #fallbackLocalStorage} otherwise.
|
||||
*/
|
||||
private final List<Consumer<CrossDomainStorage>> queuedWhileWaitingForStorage;
|
||||
|
||||
/**
|
||||
* The timeout timer. Set if the first request is received through any of the {@link CrossDomainStorage} methods and the
|
||||
* {@link #storageToUse} is not yet set. Cancelled (if not {@code null} and reset to {@code null} when the {@link MessagePort}
|
||||
* connecting to the {@code iframe} has connected successfully and the {@link #storageToUse} is set to the "real"
|
||||
* {@link CrossDomainStorageImpl}.
|
||||
*/
|
||||
private Timer timer;
|
||||
|
||||
/**
|
||||
* If -1, no timeout applies and requests will remain in the queue until a non-{@code null} delegation target is set using
|
||||
*/
|
||||
private final int timeoutInMillis;
|
||||
|
||||
private final Supplier<CrossDomainStorage> defaultSupplier;
|
||||
|
||||
/**
|
||||
* Creates an instance with no timeout.
|
||||
*/
|
||||
public DelegatingCrossDomainStorageFuture() {
|
||||
this(/* timeout in millis */ -1, /* default storage provider */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with a specified timeout and a supplier of a {@link CrossDomainStorage} in case the timeout
|
||||
* expires.
|
||||
*
|
||||
* @param timeoutInMillis
|
||||
* if -1, no timeout logic applies
|
||||
* @param defaultSupplier
|
||||
* must be non-{@code null} if {@code timeoutInMillis} is not -1
|
||||
*/
|
||||
public DelegatingCrossDomainStorageFuture(final int timeoutInMillis, Supplier<CrossDomainStorage> defaultSupplier) {
|
||||
this.queuedWhileWaitingForStorage = new LinkedList<>();
|
||||
if (defaultSupplier == null && timeoutInMillis != -1) {
|
||||
throw new IllegalArgumentException("If a timeout of "+timeoutInMillis+"ms is provided, a valid default supplier must be provided, too.");
|
||||
}
|
||||
this.timeoutInMillis = timeoutInMillis;
|
||||
this.defaultSupplier = defaultSupplier;
|
||||
}
|
||||
|
||||
public void setStorageToUse(CrossDomainStorage storageToUse) {
|
||||
if (storageToUse == null) {
|
||||
throw new NullPointerException("Must set a valid, non-null storage");
|
||||
}
|
||||
if (this.storageToUse != null) {
|
||||
throw new IllegalStateException("The storage can only be set once");
|
||||
}
|
||||
this.storageToUse = storageToUse;
|
||||
if (!queuedWhileWaitingForStorage.isEmpty()) {
|
||||
GWT.log("now executing "+queuedWhileWaitingForStorage.size()+" queued requests for cross-domain storage");
|
||||
for (final Consumer<CrossDomainStorage> queuedRequest : queuedWhileWaitingForStorage) {
|
||||
queuedRequest.accept(storageToUse);
|
||||
}
|
||||
queuedWhileWaitingForStorage.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void runOrEnqueue(Consumer<CrossDomainStorage> request) {
|
||||
if (storageToUse != null) {
|
||||
request.accept(storageToUse);
|
||||
} else {
|
||||
queuedWhileWaitingForStorage.add(request);
|
||||
if (timeoutInMillis != -1) {
|
||||
ensureTimerIsRunning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTimerIsRunning() {
|
||||
if (timer == null) {
|
||||
GWT.log("cross-domain storage iframe not ready yet; waiting "+timeoutInMillis+"ms for message port");
|
||||
timer = new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
timer = null;
|
||||
if (storageToUse == null) {
|
||||
GWT.log("cross-domain storage timeout expired, using local storage "
|
||||
+ Window.Location.getProtocol() + "//" + Window.Location.getHost()
|
||||
+ " instead of cross-domain storage");
|
||||
setStorageToUse(defaultSupplier.get());
|
||||
}
|
||||
}
|
||||
};
|
||||
timer.schedule(timeoutInMillis);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerRegistration addStorageEventHandler(Handler handler) {
|
||||
final HandlerRegistration[] result = new HandlerRegistration[1];
|
||||
runOrEnqueue(storage->result[0]=storage.addStorageEventHandler(handler));
|
||||
// not entirely safe if the addStorageEventHandler call above is really enqueued and the
|
||||
// HandlerRegistration.removeHandler call occurs before successful connection; the handler removal
|
||||
// then would fail with result[0] not yet being initialized
|
||||
return () -> runOrEnqueue(storage->{
|
||||
if (result[0] != null) {
|
||||
result[0].removeHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(String key, String value, Consumer<Void> callback) {
|
||||
runOrEnqueue(storage->storage.setItem(key, value, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItem(String key, Consumer<String> callback) {
|
||||
runOrEnqueue(storage->storage.getItem(key, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeItem(String key, Consumer<Void> callback) {
|
||||
runOrEnqueue(storage->storage.removeItem(key, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Consumer<Void> callback) {
|
||||
runOrEnqueue(storage->storage.clear(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void key(int index, Consumer<String> callback) {
|
||||
runOrEnqueue(storage->storage.key(index, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLength(Consumer<Integer> callback) {
|
||||
runOrEnqueue(storage->storage.getLength(callback));
|
||||
}
|
||||
}
|
||||
+4
-135
@@ -1,19 +1,11 @@
|
||||
package com.sap.sse.gwt.client.xdstorage.impl;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Document;
|
||||
import com.google.gwt.event.shared.HandlerRegistration;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.sap.sse.gwt.client.messaging.MessagePort;
|
||||
import com.sap.sse.gwt.client.xdstorage.CrossDomainStorage;
|
||||
import com.sap.sse.gwt.client.xdstorage.CrossDomainStorageEvent.Handler;
|
||||
import com.sap.sse.gwt.client.xdstorage.DelegatingCrossDomainStorageFuture;
|
||||
|
||||
public class CrossDomainStorageFallingBackToLocalAfterTimeout implements CrossDomainStorage {
|
||||
public class CrossDomainStorageFallingBackToLocalAfterTimeout extends DelegatingCrossDomainStorageFuture {
|
||||
/**
|
||||
* The path under which the HTML document running the {@link StorageMessagingEntryPoint} can be loaded.
|
||||
*/
|
||||
@@ -26,137 +18,14 @@ public class CrossDomainStorageFallingBackToLocalAfterTimeout implements CrossDo
|
||||
*/
|
||||
private static final int TIMEOUT_FOR_IFRAME_TO_RESPOND_IN_MILLIS = 5000;
|
||||
|
||||
/**
|
||||
* Starts out as {@code null}. The constructor makes an effort to create an {@code iframe} element in the body of
|
||||
* the {@link Document} passed to the {@link #CrossDomainStorageFallingBackToLocalAfterTimeout(Document, String)
|
||||
* constructor}. Once the {@code iframe} has been established and called back to its parent window, the
|
||||
* {@link MessagePort} used for communicating with that {@code iframe} is passed to a consumer callback which sets
|
||||
* this field to a {@link CrossDomainStorageImpl} object using that {@link MessagePort}.
|
||||
* <p>
|
||||
*
|
||||
* When a request comes in through any of the {@link CrossDomainStorage} methods and this field is still
|
||||
* {@code null}, a timer is started with the {@link #TIMEOUT_FOR_IFRAME_TO_RESPOND_IN_MILLIS} duration, and the request is
|
||||
* recorded in the {@link #queuedWhileWaitingForIframe} queue. When the timer times out, or the {@link MessagePort}
|
||||
* is established---whichever comes first---the queued requests will be executed with the now decided
|
||||
* {@link CrossDomainStorage} which is then also set to this field. In case the timer timed out, this will be
|
||||
* a {@link LocalStorage} which does not use any {@link MessagePort} but maps all requests to the local storage
|
||||
* under the local origin.
|
||||
*/
|
||||
private CrossDomainStorage storageToUse;
|
||||
|
||||
/**
|
||||
* When request methods are called through any of the {@link CrossDomainStorage} interface methods and the
|
||||
* {@link #portToStorageMessagingEntryPoint} is not ready yet (still {@code null}), requests are queued in this list
|
||||
* until the moment when the message port is set after the iframe has announced its readiness, or the timer has timed
|
||||
* out, whichever comes first. Then, the requests are worked down, one after the other, using the {@link #crossDomainStorage}
|
||||
* through the iframe if it became ready in time, or using the {@link #fallbackLocalStorage} otherwise.
|
||||
*/
|
||||
private final List<Consumer<CrossDomainStorage>> queuedWhileWaitingForIframe;
|
||||
|
||||
/**
|
||||
* The timeout timer. Set if the first request is received through any of the {@link CrossDomainStorage} methods and the
|
||||
* {@link #storageToUse} is not yet set. Cancelled (if not {@code null} and reset to {@code null} when the {@link MessagePort}
|
||||
* connecting to the {@code iframe} has connected successfully and the {@link #storageToUse} is set to the "real"
|
||||
* {@link CrossDomainStorageImpl}.
|
||||
*/
|
||||
private Timer timer;
|
||||
|
||||
public CrossDomainStorageFallingBackToLocalAfterTimeout(Document documentInWhichToInsertMessagingIframe,
|
||||
String baseUrlForStorageMessagingEntryPoint) {
|
||||
this.queuedWhileWaitingForIframe = new LinkedList<>();
|
||||
super(TIMEOUT_FOR_IFRAME_TO_RESPOND_IN_MILLIS, ()->new LocalStorage());
|
||||
MessagePort.createInDocument(documentInWhichToInsertMessagingIframe,
|
||||
baseUrlForStorageMessagingEntryPoint+(baseUrlForStorageMessagingEntryPoint.endsWith("/")?"":"/")+STORAGE_MESSAGING_ENTRY_POINT_PATH,
|
||||
result->{
|
||||
GWT.log("connection to cross-domain storage at "+baseUrlForStorageMessagingEntryPoint+" established");
|
||||
if (timer != null) {
|
||||
GWT.log("cancelling cross-domain storage timout listener that would have resorted to local storage");
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
setStorageToUseAndWorkDownQueue(new CrossDomainStorageImpl(result, baseUrlForStorageMessagingEntryPoint));
|
||||
setStorageToUse(new CrossDomainStorageImpl(result, baseUrlForStorageMessagingEntryPoint));
|
||||
});
|
||||
}
|
||||
|
||||
private void setStorageToUseAndWorkDownQueue(CrossDomainStorage storageToUse) {
|
||||
this.storageToUse = storageToUse;
|
||||
if (!queuedWhileWaitingForIframe.isEmpty()) {
|
||||
GWT.log("now executing "+queuedWhileWaitingForIframe.size()+" queued requests for cross-domain storage");
|
||||
for (final Consumer<CrossDomainStorage> queuedRequest : queuedWhileWaitingForIframe) {
|
||||
queuedRequest.accept(storageToUse);
|
||||
}
|
||||
queuedWhileWaitingForIframe.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void runOrEnqueue(Consumer<CrossDomainStorage> request) {
|
||||
if (storageToUse != null) {
|
||||
request.accept(storageToUse);
|
||||
} else {
|
||||
queuedWhileWaitingForIframe.add(request);
|
||||
ensureTimerIsRunning();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTimerIsRunning() {
|
||||
if (timer == null) {
|
||||
GWT.log("cross-domain storage iframe not ready yet; waiting "+TIMEOUT_FOR_IFRAME_TO_RESPOND_IN_MILLIS+" for message port");
|
||||
timer = new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
timer = null;
|
||||
if (storageToUse == null) {
|
||||
GWT.log("cross-domain storage timeout expired, using local storage "
|
||||
+ Window.Location.getProtocol() + "//" + Window.Location.getHost()
|
||||
+ " instead of cross-domain storage");
|
||||
setStorageToUseAndWorkDownQueue(new LocalStorage());
|
||||
}
|
||||
}
|
||||
};
|
||||
timer.schedule(TIMEOUT_FOR_IFRAME_TO_RESPOND_IN_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerRegistration addStorageEventHandler(Handler handler) {
|
||||
final HandlerRegistration[] result = new HandlerRegistration[1];
|
||||
runOrEnqueue(storage->result[0]=storage.addStorageEventHandler(handler));
|
||||
// not entirely safe if the addStorageEventHandler call above is really enqueued and the
|
||||
// HandlerRegistration.removeHandler call occurs before successful connection; the handler removal
|
||||
// then would fail with result[0] not yet being initialized
|
||||
return () -> runOrEnqueue(storage->{
|
||||
if (result[0] != null) {
|
||||
result[0].removeHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(String key, String value, Consumer<Void> callback) {
|
||||
runOrEnqueue(storage->storage.setItem(key, value, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItem(String key, Consumer<String> callback) {
|
||||
runOrEnqueue(storage->storage.getItem(key, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeItem(String key, Consumer<Void> callback) {
|
||||
runOrEnqueue(storage->storage.removeItem(key, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Consumer<Void> callback) {
|
||||
runOrEnqueue(storage->storage.clear(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void key(int index, Consumer<String> callback) {
|
||||
runOrEnqueue(storage->storage.key(index, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLength(Consumer<Integer> callback) {
|
||||
runOrEnqueue(storage->storage.getLength(callback));
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,7 @@ Require-Bundle: javax.servlet;bundle-version="3.1.0",
|
||||
com.sap.sse.security.common,
|
||||
com.sap.sse.mail
|
||||
Web-ContextPath: /security/ui
|
||||
Export-Package: com.sap.sse.security.shared,
|
||||
com.sap.sse.security.ui.authentication,
|
||||
Export-Package: com.sap.sse.security.ui.authentication,
|
||||
com.sap.sse.security.ui.authentication.app,
|
||||
com.sap.sse.security.ui.authentication.confirm,
|
||||
com.sap.sse.security.ui.authentication.create,
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
package com.sap.sse.security.shared;
|
||||
package com.google.gwt.user.client.rpc.core.com.sap.sse.security.shared;
|
||||
|
||||
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
|
||||
import com.google.gwt.user.client.rpc.SerializationException;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamReader;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
|
||||
import com.sap.sse.security.shared.UnauthorizedException;
|
||||
|
||||
public class UnauthorizedException_CustomFieldSerializer extends CustomFieldSerializer<UnauthorizedException> {
|
||||
@Override
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
package com.sap.sse.security.shared;
|
||||
package com.google.gwt.user.client.rpc.core.com.sap.sse.security.shared;
|
||||
|
||||
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
|
||||
import com.google.gwt.user.client.rpc.SerializationException;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamReader;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
|
||||
import com.sap.sse.security.shared.UserGroupManagementException;
|
||||
|
||||
public class UserGroupManagementException_CustomFieldSerializer extends CustomFieldSerializer<UserGroupManagementException> {
|
||||
@Override
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
package com.sap.sse.security.shared;
|
||||
package com.google.gwt.user.client.rpc.core.com.sap.sse.security.shared;
|
||||
|
||||
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
|
||||
import com.google.gwt.user.client.rpc.SerializationException;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamReader;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
|
||||
import com.sap.sse.security.shared.UserManagementException;
|
||||
|
||||
public class UserManagementException_CustomFieldSerializer extends CustomFieldSerializer<UserManagementException> {
|
||||
@Override
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.google.gwt.user.client.rpc.core.com.sap.sse.security.ui.shared;
|
||||
|
||||
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
|
||||
import com.google.gwt.user.client.rpc.SerializationException;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamReader;
|
||||
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
|
||||
import com.sap.sse.security.ui.shared.SecurityServiceSharingDTO;
|
||||
|
||||
public class SecurityServiceSharingDTO_CustomFieldSerializer extends CustomFieldSerializer<SecurityServiceSharingDTO> {
|
||||
@Override
|
||||
public boolean hasCustomInstantiateInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityServiceSharingDTO instantiateInstance(SerializationStreamReader streamReader) throws SerializationException {
|
||||
return instantiate(streamReader);
|
||||
}
|
||||
|
||||
public static SecurityServiceSharingDTO instantiate(SerializationStreamReader streamReader) throws SerializationException {
|
||||
final String jSessionIdCookieDomain = streamReader.readString();
|
||||
final String baseUrlForCrossDomainStorage = streamReader.readString();
|
||||
return new SecurityServiceSharingDTO(jSessionIdCookieDomain, baseUrlForCrossDomainStorage);
|
||||
}
|
||||
|
||||
public static void deserialize(SerializationStreamReader streamReader, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
// handled by instantiateInstance
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeInstance(SerializationStreamReader streamReader, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
deserialize(streamReader, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeInstance(SerializationStreamWriter streamWriter, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
serialize(streamWriter, instance);
|
||||
}
|
||||
|
||||
public static void serialize(SerializationStreamWriter streamWriter, SecurityServiceSharingDTO instance)
|
||||
throws SerializationException {
|
||||
streamWriter.writeString(instance.getjSessionIdCookieDomain());
|
||||
streamWriter.writeString(instance.getBaseUrlForCrossDomainStorage());
|
||||
}
|
||||
}
|
||||
+3
@@ -29,6 +29,7 @@ import com.sap.sse.security.shared.dto.UserGroupDTO;
|
||||
import com.sap.sse.security.shared.dto.WildcardPermissionWithSecurityDTO;
|
||||
import com.sap.sse.security.ui.oauth.client.CredentialDTO;
|
||||
import com.sap.sse.security.ui.oauth.shared.OAuthException;
|
||||
import com.sap.sse.security.ui.shared.SecurityServiceSharingDTO;
|
||||
import com.sap.sse.security.ui.shared.SuccessInfo;
|
||||
|
||||
public interface UserManagementService extends RemoteService {
|
||||
@@ -218,4 +219,6 @@ public interface UserManagementService extends RemoteService {
|
||||
throws UserManagementException, org.apache.shiro.authz.UnauthorizedException;
|
||||
|
||||
Boolean userGroupExists(String userGroupName) throws org.apache.shiro.authz.UnauthorizedException;
|
||||
|
||||
SecurityServiceSharingDTO getSharingConfiguration();
|
||||
}
|
||||
+7
@@ -24,6 +24,7 @@ import com.sap.sse.security.shared.dto.UserDTO;
|
||||
import com.sap.sse.security.shared.dto.UserGroupDTO;
|
||||
import com.sap.sse.security.shared.dto.WildcardPermissionWithSecurityDTO;
|
||||
import com.sap.sse.security.ui.oauth.client.CredentialDTO;
|
||||
import com.sap.sse.security.ui.shared.SecurityServiceSharingDTO;
|
||||
import com.sap.sse.security.ui.shared.SuccessInfo;
|
||||
|
||||
public interface UserManagementServiceAsync {
|
||||
@@ -193,4 +194,10 @@ public interface UserManagementServiceAsync {
|
||||
void getRolesAndPermissionsForUser(String username, AsyncCallback<RolesAndPermissionsForUserDTO> callback);
|
||||
|
||||
void userGroupExists(String userGroupName, AsyncCallback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* Provides information about whether and how the security service to which this RPC service talks is shared
|
||||
* across a server landscape with different domain/sub-domain constellations.
|
||||
*/
|
||||
void getSharingConfiguration(AsyncCallback<SecurityServiceSharingDTO> callback);
|
||||
}
|
||||
+48
-11
@@ -4,11 +4,11 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.user.client.Random;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
@@ -24,6 +24,8 @@ import com.sap.sse.gwt.client.Storage;
|
||||
import com.sap.sse.gwt.client.StorageEvent;
|
||||
import com.sap.sse.gwt.client.StorageEvent.Handler;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.xdstorage.CrossDomainStorage;
|
||||
import com.sap.sse.gwt.client.xdstorage.DelegatingCrossDomainStorageFuture;
|
||||
import com.sap.sse.security.shared.HasPermissions;
|
||||
import com.sap.sse.security.shared.HasPermissions.Action;
|
||||
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
|
||||
@@ -39,6 +41,7 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
|
||||
import com.sap.sse.security.shared.impl.SecuredSecurityTypes.ServerActions;
|
||||
import com.sap.sse.security.ui.client.i18n.StringMessages;
|
||||
import com.sap.sse.security.ui.oauth.client.util.ClientUtils;
|
||||
import com.sap.sse.security.ui.shared.SecurityServiceSharingDTO;
|
||||
import com.sap.sse.security.ui.shared.SuccessInfo;
|
||||
|
||||
/**
|
||||
@@ -69,7 +72,6 @@ public class UserService {
|
||||
*/
|
||||
private static final String LOCAL_STORAGE_UPDATE_KEY = "current-user-has-changed";
|
||||
|
||||
|
||||
/**
|
||||
* Storage key to remember when a user was authenticated or dismissed the login hint the last time.
|
||||
*/
|
||||
@@ -97,17 +99,53 @@ public class UserService {
|
||||
private ServerInfoDTO serverInfo;
|
||||
|
||||
private final Set<HasPermissions> allKnownHasPermissions;
|
||||
|
||||
/**
|
||||
* The storage configured based on what {@link UserManagementServiceAsync#getSharingConfiguration(AsyncCallback)} has returned.
|
||||
*/
|
||||
private final DelegatingCrossDomainStorageFuture crossDomainStorage;
|
||||
|
||||
public UserService(UserManagementServiceAsync userManagementService) {
|
||||
this.id = ""+(System.currentTimeMillis() * Random.nextInt()); // something pretty random
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.userManagementService = userManagementService;
|
||||
handlers = new HashSet<>();
|
||||
allKnownHasPermissions = new HashSet<>();
|
||||
crossDomainStorage = new DelegatingCrossDomainStorageFuture();
|
||||
initializeCrossDomainStorage();
|
||||
Util.addAll(SecuredSecurityTypes.getAllInstances(), allKnownHasPermissions);
|
||||
registerStorageEventHandler();
|
||||
updateUser(/* notifyOtherInstances */ false);
|
||||
}
|
||||
|
||||
private void initializeCrossDomainStorage() {
|
||||
assert userManagementService != null;
|
||||
assert crossDomainStorage != null;
|
||||
userManagementService.getSharingConfiguration(new AsyncCallback<SecurityServiceSharingDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
Notification.notify(caught.getMessage(), NotificationType.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SecurityServiceSharingDTO result) {
|
||||
crossDomainStorage.setStorageToUse(CrossDomainStorage.create(result.getBaseUrlForCrossDomainStorage()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this instead of {@link Storage#getLocalStorageIfSupported()}. The resulting {@link CrossDomainStorage} object
|
||||
* is configured by the server settings for sharing or isolation of the security service. Note that the methods all
|
||||
* work asynchronously, talking to a callback that the caller needs to pass. It can also happen that first requests
|
||||
* initially have to wait a few milliseconds because the configuration needs to be loaded from the back-end first
|
||||
* (which happens immediately when this {@link UserService} is constructed), and if a shared storage is configured,
|
||||
* an {@code iframe} needs to be loaded, and request processing is delayed until the loading of that {@code iframe}
|
||||
* has completed.
|
||||
*/
|
||||
public CrossDomainStorage getStorage() {
|
||||
return crossDomainStorage;
|
||||
}
|
||||
|
||||
private void registerStorageEventHandler() {
|
||||
Storage.addStorageEventHandler(new Handler() {
|
||||
@Override
|
||||
@@ -295,18 +333,18 @@ public class UserService {
|
||||
*/
|
||||
public void getPreference(String key,
|
||||
final AsyncCallback<String> callback) {
|
||||
String username = getCurrentUser().getName(); // TODO: Can username be determined via session on server-side
|
||||
String username = getCurrentUser().getName();
|
||||
getUserManagementService().getPreference(username, key, callback);
|
||||
}
|
||||
|
||||
public void getPreferences(List<String> keys,
|
||||
final AsyncCallback<Map<String, String>> callback) {
|
||||
String username = getCurrentUser().getName(); // TODO: Can username be determined via session on server-side
|
||||
String username = getCurrentUser().getName();
|
||||
getUserManagementService().getPreferences(username, keys, callback);
|
||||
}
|
||||
|
||||
public void getAllPreferences(final AsyncCallback<Map<String, String>> callback) {
|
||||
String username = getCurrentUser().getName(); // TODO: Can username be determined via session on server-side
|
||||
String username = getCurrentUser().getName();
|
||||
getUserManagementService().getAllPreferences(username, callback);
|
||||
}
|
||||
|
||||
@@ -319,13 +357,13 @@ public class UserService {
|
||||
* Serialized settings as {@link String} containing the preferences
|
||||
*/
|
||||
public void setPreference(String key, String serializedSettings, final AsyncCallback<Void> callback) {
|
||||
String username = getCurrentUser().getName(); // TODO: Can username be determined via session on server-side
|
||||
String username = getCurrentUser().getName();
|
||||
getUserManagementService().setPreference(username, key, serializedSettings, callback);
|
||||
}
|
||||
|
||||
public void setPreferences(Map<String, String> keyValuePairs,
|
||||
final AsyncCallback<Void> callback) {
|
||||
String username = getCurrentUser().getName(); // TODO: Can username be determined via session on server-side
|
||||
String username = getCurrentUser().getName();
|
||||
getUserManagementService().setPreferences(username, keyValuePairs, callback);
|
||||
}
|
||||
|
||||
@@ -338,16 +376,15 @@ public class UserService {
|
||||
* @see AbstractGenericSerializableSettings
|
||||
*/
|
||||
public void unsetPreference(String key) {
|
||||
String username = getCurrentUser().getName(); // TODO: Can username be determined via session on server-side
|
||||
String username = getCurrentUser().getName();
|
||||
getUserManagementService().unsetPreference(username, key, new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
// TODO What to do in case of failure?
|
||||
Notification.notify(caught.getMessage(), NotificationType.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
// TODO Do anything in case of success?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+7
@@ -67,6 +67,7 @@ import com.sap.sse.security.shared.impl.UserGroupImpl;
|
||||
import com.sap.sse.security.ui.client.UserManagementService;
|
||||
import com.sap.sse.security.ui.oauth.client.CredentialDTO;
|
||||
import com.sap.sse.security.ui.oauth.shared.OAuthException;
|
||||
import com.sap.sse.security.ui.shared.SecurityServiceSharingDTO;
|
||||
import com.sap.sse.security.ui.shared.SuccessInfo;
|
||||
|
||||
public class UserManagementServiceImpl extends RemoteServiceServlet implements UserManagementService {
|
||||
@@ -1152,4 +1153,10 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U
|
||||
.getStringPermissionForTypeRelativeIdentifier(DefaultActions.READ, typeRelativeObjectIdentifier));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityServiceSharingDTO getSharingConfiguration() {
|
||||
return new SecurityServiceSharingDTO(getSecurityService().getSharedAcrossSubdomainsOf(),
|
||||
getSecurityService().getBaseUrlForCrossDomainStorage());
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.sap.sse.security.ui.shared;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.sap.sse.gwt.client.xdstorage.CrossDomainStorage;
|
||||
import com.sap.sse.security.ui.client.UserManagementServiceAsync;
|
||||
|
||||
/**
|
||||
* A security service ({@code SecurityService}) can be operated in isolated or shared mode. When in shared mode, the
|
||||
* Shiro configuration will deliver session cookies with a shared parent domain so that it will be used regardless the
|
||||
* sub-domain that the user is currently navigating in. In isolated mode the session cookie's domain will be specific to
|
||||
* the server URL used to access the application.
|
||||
* <p>
|
||||
*
|
||||
* Likewise, the {@link CrossDomainStorage} needs to be configured differently, depending on whether the service
|
||||
* operates in isolated or shared mode. When in isolated mode, the "origin" used to identify the application's local and
|
||||
* session storage is the URL used to access the application. When in shared mode, different sub-domains would lead to
|
||||
* different local storages, so instead a common origin from which the {@code /gwt-base/StorageMessaging.html} entry
|
||||
* point is available is used to manage the storage. See {@link CrossDomainStorage} for details.
|
||||
* <p>
|
||||
*
|
||||
* This data transfer object describes the configuration provided by the server, available from the
|
||||
* {@link UserManagementServiceAsync#getSharingConfiguration(com.google.gwt.user.client.rpc.AsyncCallback)} method.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class SecurityServiceSharingDTO implements Serializable {
|
||||
private static final long serialVersionUID = 264755515738862492L;
|
||||
private final String jSessionIdCookieDomain;
|
||||
private final String baseUrlForCrossDomainStorage;
|
||||
|
||||
public SecurityServiceSharingDTO(String jSessionIdCookieDomain, String baseUrlForCrossDomainStorage) {
|
||||
super();
|
||||
this.jSessionIdCookieDomain = jSessionIdCookieDomain;
|
||||
this.baseUrlForCrossDomainStorage = baseUrlForCrossDomainStorage;
|
||||
}
|
||||
|
||||
public String getjSessionIdCookieDomain() {
|
||||
return jSessionIdCookieDomain;
|
||||
}
|
||||
|
||||
public String getBaseUrlForCrossDomainStorage() {
|
||||
return baseUrlForCrossDomainStorage;
|
||||
}
|
||||
}
|
||||
@@ -624,13 +624,25 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica
|
||||
|
||||
/**
|
||||
* In case this security service is shared across multiple replica sets that are published under different
|
||||
* subdomains, session cookies and the local store shall be arranged such that sessions and content are identified
|
||||
* regardless the subdomain used. For example, if there are two events, A, and B, published through
|
||||
* {@code a.sapsailing.com} and {@code b.sapsailing.com}, respectively, by default the full subdomain name will be
|
||||
* sub-domains, session cookies and the local store shall be arranged such that sessions and content are identified
|
||||
* regardless the sub-domain used. For example, if there are two events, A, and B, published through
|
||||
* {@code a.sapsailing.com} and {@code b.sapsailing.com}, respectively, by default the full sub-domain name will be
|
||||
* used for the {@code JSESSIONID} cookie as well as for the identification of content in the browser's local store.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getSharedAcrossSubdomainsOf();
|
||||
|
||||
/**
|
||||
* In the browser client, a {@code CrossDomainStorage} implementation is used to either run requests for a local
|
||||
* browser storage against the application's local storage under the URL/origin used to access the application, or a
|
||||
* shared local storage across several sub-domains may be configured so that data stored while in the context of one
|
||||
* sub-domain is also available to the application when loaded from a different sub-domain.
|
||||
*
|
||||
* @return the base URL where the {@code /gwt-base/StorageMessaging.html} entry point can be found; if {@code null},
|
||||
* clients should configure their {@code CrossDomainStorage} such that the application's origin is used for
|
||||
* isolated local storage; otherwise, the result should be used to get a {@code CrossDomainStorage}
|
||||
* implementation that accesses a local store shared across all application instances that use the same base
|
||||
* URL for cross-domain storage.
|
||||
*/
|
||||
String getBaseUrlForCrossDomainStorage();
|
||||
|
||||
}
|
||||
@@ -72,10 +72,26 @@ public class Activator implements BundleActivator {
|
||||
* <p>
|
||||
*
|
||||
* The value can be specified by a system property whose name is provided by
|
||||
* {@link #SHARED_ACROSS_SUBDOMAINS_OF_PROPERTY_NAME}.
|
||||
* {@link #SHARED_ACROSS_SUBDOMAINS_OF_PROPERTY_NAME}. If a non-{@code null} value is provided for
|
||||
* {@link #baseUrlForCrossDomainStorage} then a non-{@code null} value should be provided here, too.
|
||||
*/
|
||||
private final String sharedAcrossSubdomainsOf;
|
||||
|
||||
public static final String BASE_URL_FOR_CROSS_DOMAIN_STORAGE_PROPERTY_NAME = "security.baseUrlForCrossDomainStorage";
|
||||
/**
|
||||
* Should be {@code null} if the security service instantiated by the bundle activated by this activator is not
|
||||
* shared across different sub-domains; otherwise, the base URL where {@code /gwt-base/StorageMessaging.html} can be
|
||||
* loaded from shall be provided, and this base URL will then form the "origin" for the local browser storage used
|
||||
* for session storage and other local data stored in the browser which will then be available to all servers using
|
||||
* the same setting fo rthis field. Example value: {@code "https://www.sapsailing.com"}.
|
||||
* <p>
|
||||
*
|
||||
* The value can be specified by a system property whose name is provided by
|
||||
* {@link #BASE_URL_FOR_CROSS_DOMAIN_STORAGE_PROPERTY_NAME}. If a non-{@code null} value is provided for
|
||||
* {@link #sharedAcrossSubdomainsOf} then a non-{@code null} value should be provided here, too.
|
||||
*/
|
||||
private final String baseUrlForCrossDomainStorage;
|
||||
|
||||
public static void setTestStores(UserStore theTestUserStore, AccessControlStore theTestAccessControlStore) {
|
||||
testUserStore = theTestUserStore;
|
||||
testAccessControlStore = theTestAccessControlStore;
|
||||
@@ -101,6 +117,13 @@ public class Activator implements BundleActivator {
|
||||
|
||||
public Activator() {
|
||||
sharedAcrossSubdomainsOf = System.getProperty(SHARED_ACROSS_SUBDOMAINS_OF_PROPERTY_NAME);
|
||||
baseUrlForCrossDomainStorage = System.getProperty(BASE_URL_FOR_CROSS_DOMAIN_STORAGE_PROPERTY_NAME);
|
||||
if ((sharedAcrossSubdomainsOf == null) != (baseUrlForCrossDomainStorage == null)) {
|
||||
logger.warning("This looks like an inconsistent configuration. The security service uses "
|
||||
+ sharedAcrossSubdomainsOf + " as shared cookie domain but " + baseUrlForCrossDomainStorage
|
||||
+ " as cross-domain storage origin. " + " Either leave both null or set both consistently, e.g., "
|
||||
+ "to \"sapsailing.com\" and \"https://www.sapsailing.com\", respectively");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +173,7 @@ public class Activator implements BundleActivator {
|
||||
hasPermissionsProviderTracker.open();
|
||||
SecurityServiceImpl initialSecurityService = new SecurityServiceImpl(
|
||||
ServiceTrackerFactory.createAndOpen(context, MailService.class), userStore, accessControlStore,
|
||||
new OSGIHasPermissionsProvider(hasPermissionsProviderTracker), sharedAcrossSubdomainsOf);
|
||||
new OSGIHasPermissionsProvider(hasPermissionsProviderTracker), sharedAcrossSubdomainsOf, baseUrlForCrossDomainStorage);
|
||||
initialSecurityService.initialize();
|
||||
securityService.complete(initialSecurityService);
|
||||
registration = context.registerService(SecurityService.class.getName(), initialSecurityService, null);
|
||||
|
||||
@@ -188,6 +188,8 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
|
||||
private String sharedAcrossSubdomainsOf;
|
||||
|
||||
private String baseUrlForCrossDomainStorage;
|
||||
|
||||
static {
|
||||
shiroConfiguration = new Ini();
|
||||
shiroConfiguration.loadFromPath("classpath:shiro.ini");
|
||||
@@ -219,7 +221,8 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
*/
|
||||
public SecurityServiceImpl(ServiceTracker<MailService, MailService> mailServiceTracker, UserStore userStore,
|
||||
AccessControlStore accessControlStore, HasPermissionsProvider hasPermissionsProvider) {
|
||||
this(mailServiceTracker, userStore, accessControlStore, hasPermissionsProvider, /* sharedAcrossSubdomainsOf */ null);
|
||||
this(mailServiceTracker, userStore, accessControlStore, hasPermissionsProvider,
|
||||
/* sharedAcrossSubdomainsOf */ null, /* baseUrlForCrossDomainStorage */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,10 +232,12 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
* be shared as well.
|
||||
*/
|
||||
public SecurityServiceImpl(ServiceTracker<MailService, MailService> mailServiceTracker, UserStore userStore,
|
||||
AccessControlStore accessControlStore, HasPermissionsProvider hasPermissionsProvider, String sharedAcrossSubdomainsOf) {
|
||||
AccessControlStore accessControlStore, HasPermissionsProvider hasPermissionsProvider,
|
||||
String sharedAcrossSubdomainsOf, String baseUrlForCrossDomainStorage) {
|
||||
logger.info("Initializing Security Service with user store " + userStore);
|
||||
operationsSentToMasterForReplication = new HashSet<>();
|
||||
this.sharedAcrossSubdomainsOf = sharedAcrossSubdomainsOf;
|
||||
this.baseUrlForCrossDomainStorage = baseUrlForCrossDomainStorage;
|
||||
this.operationExecutionListeners = new ConcurrentHashMap<>();
|
||||
this.store = userStore;
|
||||
this.accessControlStore = accessControlStore;
|
||||
@@ -2086,6 +2091,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
}
|
||||
logger.info("Reading isSharedAcrossSubdomains...");
|
||||
sharedAcrossSubdomainsOf = (String) is.readObject();
|
||||
baseUrlForCrossDomainStorage = (String) is.readObject();
|
||||
logger.info("...as "+sharedAcrossSubdomainsOf);
|
||||
logger.info("Done filling SecurityService");
|
||||
}
|
||||
@@ -2096,6 +2102,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
objectOutputStream.writeObject(store);
|
||||
objectOutputStream.writeObject(accessControlStore);
|
||||
objectOutputStream.writeObject(sharedAcrossSubdomainsOf);
|
||||
objectOutputStream.writeObject(baseUrlForCrossDomainStorage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2459,4 +2466,9 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
public String getSharedAcrossSubdomainsOf() {
|
||||
return sharedAcrossSubdomainsOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrlForCrossDomainStorage() {
|
||||
return baseUrlForCrossDomainStorage;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user