Skip to content
Open
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
Binary file modified app/credentials-management/doc/images/CredentialsManagement.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 14 additions & 7 deletions app/credentials-management/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@ Further, Phoebus may be configured to store the credentials entered by the user
In order to also support an explicit logout capability, the Credentials Management application offers means to
remove stored credentials.

In some cases an explicit login procedure can be useful, e.g. login to service for the purpose of storing
user credentials and thereby support automated creation of logbook entries.

The application is launched using the dedicated button in the (bottom) status bar.

The below screen shot shows an example where credentials have been stored for the "logbook" scope,
plus an option to login to the "<remote service>" scope. User may also choose to "logout" from all scopes,
The below screen shot shows an example where credentials have been stored for the "Logbook" scope,
plus an option to login to the "<remote service>" scope. User may also choose to "Logout from all" services,
i.e. to remove all stored credentials.

.. image:: images/CredentialsManagement.png

If no credentials are stored in the credentials store, and if no services supporting authentication have been configured,
If no credentials are stored in the credentials store, and if no services supporting authentication are available,
the Credentials Management UI will show a static message:

.. image:: images/CredentialsManagement_Empty.png
.. image:: images/CredentialsManagement_empty.png

The "Login To All" button can be used to login to all services as a single action.
In this case credentials entered in the text fields of toolbar at the top are used for all services, irrespective of
the credentials (if any) entered in other input fields.

If login to a service fails (service off-line or invalid credentials), this is indicated in the "Login Result" column.

.. image:: images/CredentialsManagement_one_failed.png

If on the other hand login succeeds to a single or all services, the dialog is closed automatically.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ public CredentialsManagementStage(List<ServiceAuthenticationProvider> authentica
try {
fxmlLoader.load();
Scene scene = new Scene(fxmlLoader.getRoot());
scene.getStylesheets().add(getClass().getResource("/css/credentials-management-style.css").toExternalForm());
setTitle(Messages.Title);
setScene(scene);
} catch (Exception exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class Messages {
public static String SecureStoreErrorTitle;
public static String SecureStoreErrorBody;
public static String Title;
public static String ServiceConnectionFailure;
public static String UnknownError;
public static String UserNotAuthenticated;

static
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
.text-field-styling{
-fx-padding: 5px 3px 3px 3px;
-fx-border-insets: 5px 3px 3px 3px;
-fx-border-insets: 5px 3px 3px 2px;
-fx-background-insets: 5px 3px 3px 3px;
-fx-border-color: #cdcdcd;
-fx-border-radius: 3px;
}

.table-view .table-column{
-fx-alignment:center;
-fx-alignment:CENTER;
}

.table-view .table-cell{
-fx-font-weight: bold;
.table-view .column-header > .label{
-fx-alignment: CENTER-LEFT;
-fx-font-size: 14px;
-fx-padding: 5px 3px 3px 3px;
}

.button-style{
-fx-pref-width: 100px;
}

.error{
-fx-text-fill: red;
}


Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.layout.*?>

<BorderPane id="parent" fx:id="parent" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.phoebus.applications.credentialsmanagement.CredentialsManagementController">
<BorderPane id="parent" fx:id="parent" prefHeight="400.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.phoebus.applications.credentialsmanagement.CredentialsManagementController">
<top>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<ToolBar prefHeight="40.0" BorderPane.alignment="CENTER">
<items>
<Button fx:id="clearAllCredentialsButton" mnemonicParsing="false" onAction="#logOutFromAll" text="%LogOutFromAll">
<tooltip>
<Tooltip text="%ClearAllCredentials" />
</tooltip>
</Button>
<TextField fx:id="loginToAllUsernameTextField" promptText="%Username" />
<PasswordField fx:id="loginToAllPasswordTextField" promptText="%Password" />
<Button fx:id="loginToAllButton" mnemonicParsing="false" onAction="#loginToAll" prefWidth="120.0" text="%LoginToAll" />
<Label maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS" />
<Button fx:id="logoutFromAllButton" mnemonicParsing="false" onAction="#logoutFromAll" prefWidth="120.0" text="%LogoutFromAll" />
</items>
</ToolBar>
</top>
Expand All @@ -40,23 +40,31 @@
<Label text="%NoCredentialsFound" />
</placeholder>
<columns>
<TableColumn fx:id="scopeColumn" prefWidth="${parent.width * 0.2}" text="%Scope">
<TableColumn fx:id="scopeColumn" style="-fx-alignment: CENTER-LEFT;" text="%Scope">
<cellValueFactory>
<PropertyValueFactory property="scope" />
<PropertyValueFactory property="displayName" />
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="usernameColumn" editable="true" prefWidth="${parent.width * 0.3}" text="%UserName">
<TableColumn fx:id="usernameColumn" editable="true" prefWidth="110.0" text="%Username">
<cellValueFactory>
<PropertyValueFactory property="username" />
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="passwordColumn" editable="true" prefWidth="${parent.width * 0.3}" text="%Password">
<TableColumn fx:id="passwordColumn" editable="true" prefWidth="110.0" text="%Password">
<cellValueFactory>
<PropertyValueFactory property="password" />
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="actionButtonColumn" prefWidth="${parent.width * 0.2}" />
<TableColumn fx:id="actionButtonColumn" prefWidth="70.0" />
<TableColumn fx:id="loginResultColumn" editable="false" prefWidth="150.0" style="-fx-alignment: CENTER-LEFT;" text="%LoginResult">
<cellValueFactory>
<PropertyValueFactory property="loginResultMessage" />
</cellValueFactory>
</TableColumn>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</center>
</BorderPane>
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ ErrorDialogTitle=Operation Failed
ErrorDialogBody=Logout failed!
LoginButtonText=Login
LogoutButtonText=Logout
LogOutFromAll=Logout From All
LoginResult=Login Result
LoginToAll=Login To All
LogoutFromAll=Logout From All
NoCredentialsFound=No saved credentials and no service authentication providers configured
Password=Password
Scope=Scope
Scope=Service
SecureStoreErrorTitle=Application Launch Failed
SecureStoreErrorBody=Failed to instantiate the secure store
ServiceConnectionFailure=Unable to connect to service
Title=Credentials Management
UserName=User Name
UnkownError=Unknown error
Username=Username
UserNotAuthenticated=User not authenticated
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2025 European Spallation Source ERIC.
*/

package org.phoebus.applications.logbook.authentication;

import org.phoebus.security.tokens.AuthenticationScope;

public class OlogAuthenticationScope implements AuthenticationScope {

@Override
public String getScope() {
return "logbook";
}

@Override
public String getDisplayName() {
return "Logbook";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,25 @@
package org.phoebus.applications.logbook.authentication;

import org.phoebus.olog.es.api.OlogHttpClient;
import org.phoebus.security.authorization.AuthenticationStatus;
import org.phoebus.security.authorization.ServiceAuthenticationProvider;
import org.phoebus.security.tokens.AuthenticationScope;

import java.util.logging.Level;
import java.util.logging.Logger;

public class OlogServiceAuthenticationProvider implements ServiceAuthenticationProvider {

@Override
public void authenticate(String username, String password){
try {
OlogHttpClient.builder().build().authenticate(username, password);
} catch (Exception e) {
Logger.getLogger(OlogServiceAuthenticationProvider.class.getName())
.log(Level.WARNING, "Failed to authenticate user " + username + " with logbook service", e);
throw new RuntimeException(e);
}
private final AuthenticationScope ologAuthenticationScope;

public OlogServiceAuthenticationProvider() {
ologAuthenticationScope = new OlogAuthenticationScope();
}

@Override
public void logout(String token) {
// Not implemented for Olog. Yet?
public AuthenticationStatus authenticate(String username, String password) {
return OlogHttpClient.builder().build().authenticate(username, password);
}

@Override
public AuthenticationScope getAuthenticationScope() {
return AuthenticationScope.LOGBOOK;
return ologAuthenticationScope;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.phoebus.applications.logbook.authentication.OlogAuthenticationScope;
import org.phoebus.logbook.Attachment;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
Expand All @@ -25,15 +26,16 @@
import org.phoebus.olog.es.api.model.OlogObjectMappers;
import org.phoebus.olog.es.api.model.OlogSearchResult;
import org.phoebus.olog.es.authentication.LoginCredentials;
import org.phoebus.security.authorization.AuthenticationStatus;
import org.phoebus.security.store.SecureStore;
import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.util.http.HttpRequestMultipartBody;
import org.phoebus.util.http.QueryParamsHelper;

import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.URI;
Expand Down Expand Up @@ -112,7 +114,7 @@ public OlogHttpClient build() {
private ScopedAuthenticationToken getCredentialsFromSecureStore() {
try {
SecureStore secureStore = new SecureStore();
return secureStore.getScopedAuthenticationToken(AuthenticationScope.LOGBOOK);
return secureStore.getScopedAuthenticationToken(new OlogAuthenticationScope());
} catch (Exception e) {
Logger.getLogger(OlogHttpClient.class.getName()).log(Level.WARNING, "Unable to instantiate SecureStore", e);
return null;
Expand All @@ -128,14 +130,13 @@ public static Builder builder() {
* Disallow instantiation.
*/
private OlogHttpClient(String userName, String password) {
if(Preferences.connectTimeout > 0){
if (Preferences.connectTimeout > 0) {
httpClient = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
.followRedirects(HttpClient.Redirect.ALWAYS)
.connectTimeout(Duration.ofMillis(Preferences.connectTimeout))
.build();
}
else{
} else {
httpClient = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
.followRedirects(HttpClient.Redirect.ALWAYS)
Expand Down Expand Up @@ -358,23 +359,30 @@ public void groupLogEntries(List<Long> logEntryIds) throws LogbookException {
*
* @param userName Username, must not be <code>null</code>.
* @param password Password, must not be <code>null</code>.
* @throws Exception if the login fails, e.g. bad credentials or service off-line.
* @return An {@link AuthenticationStatus} to indicate the outcome of the login attempt.
*/
public void authenticate(String userName, String password) throws Exception {

public AuthenticationStatus authenticate(String userName, String password) {
String stringBuilder = Preferences.olog_url +
"/login";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(stringBuilder))
.header("Content-Type", CONTENT_TYPE_JSON)
.POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(new LoginCredentials(userName, password))))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 401) {
throw new Exception("Failed to login: user unauthorized");
} else if (response.statusCode() != 200) {
throw new Exception("Failed to login, got HTTP status " + response.statusCode());
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(stringBuilder))
.header("Content-Type", CONTENT_TYPE_JSON)
.POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(new LoginCredentials(userName, password))))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 401) {
LOGGER.log(Level.WARNING, "User not authenticated with logbook service");
return AuthenticationStatus.BAD_CREDENTIALS;
}
} catch (ConnectException e) {
LOGGER.log(Level.WARNING, "Cannot connect to logbook service");
return AuthenticationStatus.SERVICE_OFFLINE;
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Unable to send authentication request to service, reason unknown");
return AuthenticationStatus.UNKNOWN_ERROR;
}
return AuthenticationStatus.AUTHENTICATED;
}

/**
Expand Down Expand Up @@ -469,7 +477,7 @@ public InputStream getAttachment(Long logId, String attachmentName) {
.GET()
.build();
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
if(response.statusCode() >= 300) {
if (response.statusCode() >= 300) {
LOGGER.log(Level.WARNING, "failed to obtain attachment: " + new String(response.body().readAllBytes()));
return null;
}
Expand Down Expand Up @@ -556,7 +564,7 @@ public LogTemplate saveTemplate(LogTemplate template) throws LogbookException {
}

@Override
public Collection<LogEntryLevel> listLevels(){
public Collection<LogEntryLevel> listLevels() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(Preferences.olog_url + "/levels"))
.GET()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2025 European Spallation Source ERIC.
*/

package org.phoebus.olog.api;

import org.phoebus.security.tokens.AuthenticationScope;

public class OlogAuthenticationScope implements AuthenticationScope {

@Override
public String getScope() {
return "logbook";
}

@Override
public String getDisplayName() {
return "Logbook";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public OlogClientBuilder password(String password) {
private ScopedAuthenticationToken getCredentialsFromSecureStore() {
try {
SecureStore secureStore = new SecureStore();
return secureStore.getScopedAuthenticationToken(AuthenticationScope.LOGBOOK);
return secureStore.getScopedAuthenticationToken(new OlogAuthenticationScope());
} catch (Exception e) {
Logger.getLogger(OlogClient.class.getName()).log(Level.WARNING, "Unable to instantiate SecureStore", e);
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.phoebus.applications.logbook.authentication.OlogAuthenticationScope;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.logbook.LogClient;
import org.phoebus.logbook.LogEntry;
Expand Down Expand Up @@ -202,7 +203,7 @@ public void initialize() {
contextMenu.setOnShowing(e -> {
try {
SecureStore store = new SecureStore();
ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.LOGBOOK);
ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(new OlogAuthenticationScope());
userHasSignedIn.set(scopedAuthenticationToken != null);
} catch (Exception ex) {
logger.log(Level.WARNING, "Secure Store file not found.", ex);
Expand Down
Loading