Skip to content

Commit

Permalink
Merge pull request #10 from qupath/login-logout-from-browser
Browse files Browse the repository at this point in the history
Login logout from browser
  • Loading branch information
Rylern authored Feb 16, 2024
2 parents f59aadd + 12c4909 commit 1e8e488
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 150 deletions.
183 changes: 112 additions & 71 deletions src/main/java/qupath/ext/omero/core/WebClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,57 @@ public class WebClient implements AutoCloseable {
private Status status;
private FailReason failReason;

/**
* Status of a client creation
*/
public enum Status {
/**
* The client creation was cancelled by the user
*/
CANCELED,
/**
* The client creation failed
*/
FAILED,
/**
* The client creation succeeded
*/
SUCCESS
}

/**
* Reason why a client creation failed
*/
public enum FailReason {
/**
* A client with the same URI is already being created
*/
ALREADY_CREATING,
/**
* The format of the provided URI is incorrect
*/
INVALID_URI_FORMAT
}

/**
* How to handle authentication when creation a connection
*/
public enum Authentication {
/**
* Enforce authentication
*/
ENFORCE,
/**
* Try to skip authentication if the server allows it
*/
TRY_TO_SKIP,
/**
* Skip authentication even if the server doesn't allow it. In this case,
* the client creation should fail.
*/
SKIP
}

private WebClient() {}

/**
Expand All @@ -83,7 +123,7 @@ private WebClient() {}
* </p>
* <p>
* This function should only be used by {@link WebClients WebClients}
* which monitors opened clients (see {@link WebClients#createClient(String, boolean, String...)}).
* which monitors opened clients (see {@link WebClients#createClient(String, Authentication, String...)}).
* </p>
* <p>
* Note that this function is not guaranteed to create a valid client. Call the
Expand All @@ -99,21 +139,21 @@ private WebClient() {}
* <p>This function is asynchronous.</p>
*
* @param uri the server URI to connect to
* @param canSkipAuthentication whether authentication can be skipped if the server allows it
* @param authentication how to handle authentication
* @param args optional arguments to authenticate (see description above)
* @return a CompletableFuture with the client
*/
static CompletableFuture<WebClient> create(URI uri, boolean canSkipAuthentication, String... args) {
return new WebClient().initialize(uri, canSkipAuthentication, args);
static CompletableFuture<WebClient> create(URI uri, Authentication authentication, String... args) {
return new WebClient().initialize(uri, authentication, args);
}

/**
* <p>Synchronous version of {@link #create(URI, boolean, String...)}.</p>
* <p>Synchronous version of {@link #create(URI, Authentication, String...)}.</p>
* <p>This function may block the calling thread for around a second.</p>
*/
static WebClient createSync(URI uri, boolean canSkipAuthentication, String... args) {
static WebClient createSync(URI uri, Authentication authentication, String... args) {
WebClient webClient = new WebClient();
webClient.initializeSync(uri, canSkipAuthentication, args);
webClient.initializeSync(uri, authentication, args);
return webClient;
}

Expand Down Expand Up @@ -302,30 +342,11 @@ public boolean canBeClosed() {
.anyMatch(server -> server instanceof OmeroImageServer omeroImageServer && omeroImageServer.getClient().equals(this)));
}

private CompletableFuture<WebClient> initialize(URI uri, boolean canSkipAuthentication, String... args) {
private CompletableFuture<WebClient> initialize(URI uri, Authentication authentication, String... args) {
return ApisHandler.create(this, uri).thenApplyAsync(apisHandler -> {
if (apisHandler.isPresent()) {
this.apisHandler = apisHandler.get();
try {
Optional<String> usernameFromArgs = getCredentialFromArgs("--username", "-u", args);
Optional<String> passwordFromArgs = getCredentialFromArgs("--password", "-p", args);

if (
(usernameFromArgs.isEmpty() || passwordFromArgs.isEmpty())
&& canSkipAuthentication
&& this.apisHandler.canSkipAuthentication().get()
) {
return LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.UNAUTHENTICATED);
} else {
return login(
usernameFromArgs.orElse(null),
passwordFromArgs.orElse(null)
).get();
}
} catch (ExecutionException | InterruptedException e) {
logger.error("Error initializing client", e);
return LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.FAILED);
}
return authenticate(uri, authentication, args);
} else {
return LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.FAILED);
}
Expand Down Expand Up @@ -361,29 +382,14 @@ private CompletableFuture<WebClient> initialize(URI uri, boolean canSkipAuthenti
});
}

private void initializeSync(URI uri, boolean canSkipAuthentication, String... args) {
private void initializeSync(URI uri, Authentication authentication, String... args) {
try {
var apisHandler = ApisHandler.create(this, uri).get();

if (apisHandler.isPresent()) {
this.apisHandler = apisHandler.get();

Optional<String> usernameFromArgs = getCredentialFromArgs("--username", "-u", args);
Optional<String> passwordFromArgs = getCredentialFromArgs("--password", "-p", args);

LoginResponse loginResponse;
if (
(usernameFromArgs.isEmpty() || passwordFromArgs.isEmpty())
&& canSkipAuthentication
&& this.apisHandler.canSkipAuthentication().get()
) {
loginResponse = LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.UNAUTHENTICATED);
} else {
loginResponse = login(
usernameFromArgs.orElse(null),
passwordFromArgs.orElse(null)
).get();
}
LoginResponse loginResponse = authenticate(uri, authentication, args);

LoginResponse.Status status = loginResponse.getStatus();
if (status.equals(LoginResponse.Status.SUCCESS) || status.equals(LoginResponse.Status.UNAUTHENTICATED)) {
Expand Down Expand Up @@ -420,32 +426,39 @@ private void initializeSync(URI uri, boolean canSkipAuthentication, String... ar
}
}

private static Optional<String> getCredentialFromArgs(
String credentialLabel,
String credentialLabelAlternative,
String... args
) {
String credential = null;
int i = 0;
while (i < args.length-1) {
String parameter = args[i++];
if (credentialLabel.equals(parameter) || credentialLabelAlternative.equals(parameter)) {
credential = args[i++];
}
private LoginResponse authenticate(URI uri, Authentication authentication, String... args) {
try {
Optional<String> usernameFromArgs = getCredentialFromArgs("--username", "-u", args);
Optional<String> passwordFromArgs = getCredentialFromArgs("--password", "-p", args);

return switch (authentication) {
case ENFORCE -> login(
usernameFromArgs.orElse(null),
passwordFromArgs.orElse(null)
).get();
case TRY_TO_SKIP -> {
if (this.apisHandler.canSkipAuthentication()) {
yield LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.UNAUTHENTICATED);
} else {
yield login(
usernameFromArgs.orElse(null),
passwordFromArgs.orElse(null)
).get();
}
}
case SKIP -> {
if (this.apisHandler.canSkipAuthentication()) {
yield LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.UNAUTHENTICATED);
} else {
logger.warn(String.format("The server %s doesn't allow browsing without being authenticated", uri));
yield LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.FAILED);
}
}
};
} catch (ExecutionException | InterruptedException e) {
logger.error("Error initializing client", e);
return LoginResponse.createNonSuccessfulLoginResponse(LoginResponse.Status.FAILED);
}

return Optional.ofNullable(credential);
}

private CompletableFuture<LoginResponse> login(@Nullable String username, @Nullable String password) {
return apisHandler.login(username, password).thenApply(loginResponse -> {
if (loginResponse.getStatus().equals(LoginResponse.Status.SUCCESS)) {
setAuthenticationInformation(loginResponse);
startTimer();
}

return loginResponse;
});
}

private void setUpPixelAPIs() {
Expand Down Expand Up @@ -479,7 +492,35 @@ private void setUpPixelAPIs() {
.filter(PixelAPI::canAccessRawPixels)
.findAny()
.orElse(availablePixelAPIs.get(0))
));
));
}

private CompletableFuture<LoginResponse> login(@Nullable String username, @Nullable String password) {
return apisHandler.login(username, password).thenApply(loginResponse -> {
if (loginResponse.getStatus().equals(LoginResponse.Status.SUCCESS)) {
setAuthenticationInformation(loginResponse);
startTimer();
}

return loginResponse;
});
}

private static Optional<String> getCredentialFromArgs(
String credentialLabel,
String credentialLabelAlternative,
String... args
) {
String credential = null;
int i = 0;
while (i < args.length-1) {
String parameter = args[i++];
if (credentialLabel.equals(parameter) || credentialLabelAlternative.equals(parameter)) {
credential = args[i++];
}
}

return Optional.ofNullable(credential);
}

private void setAuthenticationInformation(LoginResponse loginResponse) {
Expand Down
21 changes: 11 additions & 10 deletions src/main/java/qupath/ext/omero/core/WebClients.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private WebClients() {
/**
* <p>
* Create a WebClient from the server URI provided. Basically, this function will
* call {@link WebClient#create(URI, boolean, String...) WebClient.create()}
* call {@link WebClient#create(URI, WebClient.Authentication, String...)} WebClient.create()}
* and internally stores the newly created client.
* </p>
* <p>
Expand All @@ -48,11 +48,11 @@ private WebClients() {
* <p>This function is asynchronous.</p>
*
* @param url the URL of the server. It doesn't have to be the base URL of the server
* @param canSkipAuthentication whether authentication can be skipped if the server allows it
* @param args optional arguments to login. See {@link WebClient#create(URI, boolean, String...) WebClient.create()}
* @param authentication how to handle authentication
* @param args optional arguments to login. See {@link WebClient#create(URI, WebClient.Authentication, String...) WebClient.create()}
* @return a CompletableFuture with the client
*/
public static CompletableFuture<WebClient> createClient(String url, boolean canSkipAuthentication, String... args) {
public static CompletableFuture<WebClient> createClient(String url, WebClient.Authentication authentication, String... args) {
var serverURI = getServerURI(url);

if (serverURI.isPresent()) {
Expand All @@ -65,7 +65,7 @@ public static CompletableFuture<WebClient> createClient(String url, boolean canS
} else {
clientsBeingCreated.add(serverURI.get());

return WebClient.create(serverURI.get(), canSkipAuthentication, args).thenApply(client -> {
return WebClient.create(serverURI.get(), authentication, args).thenApply(client -> {
if (client.getStatus().equals(WebClient.Status.SUCCESS)) {
ClientsPreferencesManager.addURI(client.getApisHandler().getWebServerURI());
updateClients(client, Operation.ADD);
Expand All @@ -83,8 +83,8 @@ public static CompletableFuture<WebClient> createClient(String url, boolean canS

/**
* <p>
* Synchronous version of {@link #createClient(String, boolean, String...)} that calls
* {@link WebClient#createSync(URI, boolean, String...) WebClient.createSync()}.
* Synchronous version of {@link #createClient(String, WebClient.Authentication, String...)} that calls
* {@link WebClient#createSync(URI, WebClient.Authentication, String...)}.
* </p>
* <p>
* Note that this function is not guaranteed to create a valid client. Call the
Expand All @@ -93,7 +93,7 @@ public static CompletableFuture<WebClient> createClient(String url, boolean canS
* </p>
* <p>This function may block the calling thread for around a second.</p>
*/
public static WebClient createClientSync(String url, boolean canSkipAuthentication, String... args) {
public static WebClient createClientSync(String url, WebClient.Authentication authentication, String... args) {
var serverURI = getServerURI(url);

if (serverURI.isPresent()) {
Expand All @@ -106,7 +106,7 @@ public static WebClient createClientSync(String url, boolean canSkipAuthenticati
} else {
clientsBeingCreated.add(serverURI.get());

var client = WebClient.createSync(serverURI.get(), canSkipAuthentication, args);
var client = WebClient.createSync(serverURI.get(), authentication, args);
if (client.getStatus().equals(WebClient.Status.SUCCESS)) {
ClientsPreferencesManager.addURI(client.getApisHandler().getWebServerURI());
updateClients(client, Operation.ADD);
Expand All @@ -124,7 +124,8 @@ public static WebClient createClientSync(String url, boolean canSkipAuthenticati
}

/**
* Close the given client connection.
* Close the given client connection. The function may return
* before the connection is actually closed.
*
* @param client the client to remove
*/
Expand Down
Loading

0 comments on commit 1e8e488

Please sign in to comment.