Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login logout from browser #10

Merged
merged 6 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading