Skip to content

Commit

Permalink
Merge pull request #226 from OsiriX-Foundation/feat/generate-auth-hea…
Browse files Browse the repository at this point in the history
…ders-stow

Dialog to help generate Authorization Headers in the Destination STOW Form
  • Loading branch information
mhenx authored Feb 20, 2025
2 parents a602727 + 9cfe788 commit c0b1a77
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,6 @@ public class DestinationEntity implements Serializable {
// mandatory[type=stow]
private String url;

// credentials of the STOW-RS service (format is "user:password").
private String urlCredentials;

// headers for HTTP request.
private String headers;

Expand Down Expand Up @@ -190,7 +187,6 @@ protected DestinationEntity(DestinationType destinationType) {
this.port = 0;
this.useaetdest = Boolean.FALSE;
this.url = "";
this.urlCredentials = "";
this.headers = "";

this.transcodeOnlyUncompressed = false;
Expand All @@ -215,11 +211,10 @@ public static DestinationEntity ofStowEmpty() {
return new DestinationEntity(DestinationType.stow);
}

public static DestinationEntity ofStow(String description, String url, String urlCredentials, String headers) {
public static DestinationEntity ofStow(String description, String url, String headers) {
DestinationEntity destinationEntity = new DestinationEntity(DestinationType.stow);
destinationEntity.setDescription(description);
destinationEntity.setUrl(url);
destinationEntity.setUrlCredentials(urlCredentials);
destinationEntity.setHeaders(headers);
return destinationEntity;
}
Expand Down Expand Up @@ -379,14 +374,6 @@ public void setUrl(String url) {
this.url = url;
}

public String getUrlCredentials() {
return urlCredentials;
}

public void setUrlCredentials(String urlCredentials) {
this.urlCredentials = urlCredentials;
}

@Size(max = 4096, message = "Headers has more than 4096 characters")
public String getHeaders() {
return headers;
Expand Down Expand Up @@ -572,7 +559,6 @@ public boolean matchesFilter(String filterText) {
|| contains(hostname, filterText) //
|| equals(port, filterText) //
|| contains(url, filterText) //
|| contains(urlCredentials, filterText) //
|| contains(headers, filterText);
}

Expand All @@ -599,7 +585,7 @@ public String toString() {
+ ", notify=" + notify + ", notifyObjectErrorPrefix=" + notifyObjectErrorPrefix
+ ", notifyObjectPattern=" + notifyObjectPattern + ", notifyObjectValues="
+ notifyObjectValues + ", notifyInterval=" + notifyInterval + ", url=" + url
+ ", urlCredentials=" + urlCredentials + ", headers=" + headers + "]";
+ ", headers=" + headers + "]";
}
}
return "Destination [id=" + id + ", description=" + description + ", type=" + destinationType + ", notify="
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* Copyright (c) 2021 Karnak Team and other contributors.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0, or the Apache
* License, Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package org.karnak.frontend.forwardnode.edit.destination.component;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.dom.Style;

import java.util.Base64;

public class AuthHeadersGenerationDialog extends Dialog {

public static final String AUTHORIZATION_TAG = "<key>Authorization</key>";

public static final String TITLE = "Generate Authorization Header";

private static final String BASIC_AUTH = "Basic Auth";

private static final String OAUTH2 = "OAuth 2";

private static final String FIELD_WIDTH = "400px";

private static final String BUTTON_WIDTH = "200px";

private Select<String> authTypeSelect;

private final String[] authTypeSelectValues = { BASIC_AUTH, OAUTH2 };

private Button cancelButton;

private Button generateButton;

private Div divContent;

private Div divTitle;

private Div divSelectBox;

private FormLayout basicForm;

private TextField basicUsername;

private TextField basicPassword;

private FormLayout oauthForm;

private TextField oauthToken;

private final FormSTOW parentForm;

public AuthHeadersGenerationDialog(FormSTOW parentForm) {
this.parentForm = parentForm;

removeAll();
setWidth("50%");

setElement();
HorizontalLayout horizontalLayout = new HorizontalLayout(cancelButton, generateButton);
horizontalLayout.setWidthFull();
horizontalLayout.getStyle().set("justify-content", "flex-end").set("margin-top", "20px");
add(divTitle, divSelectBox, divContent, horizontalLayout);
}

private void setElement() {
divTitle = new Div();
divTitle.setText(TITLE);
divTitle.getStyle().set("font-size", "large").set("font-weight", "bolder").set("padding-bottom", "10px");

divContent = new Div();
divSelectBox = new Div();
authTypeSelect = new Select<>();
authTypeSelect.setItems(authTypeSelectValues);
authTypeSelect.setLabel("Authorization Type");
authTypeSelect.setErrorMessage("This field is mandatory");
authTypeSelect.setWidth(FIELD_WIDTH);
authTypeSelect.setEmptySelectionAllowed(false);
authTypeSelect.addValueChangeListener(value -> {
displayAuthTypeForm(value.getValue());
});

divSelectBox.add(authTypeSelect);

generateButton = new Button("Generate Headers", event -> {
if (validateFields(authTypeSelect.getValue())) {
generateAuthHeaders(authTypeSelect.getValue());
}
});
generateButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
generateButton.setWidth(BUTTON_WIDTH);
cancelButton = new Button("Cancel", event -> close());
cancelButton.setWidth(BUTTON_WIDTH);

buildForms();
}

/**
* Display the form according to the Authentication Type selected in the select box
* @param value : String corresponding to the Authentication Type chosen in the select box
*/
private void displayAuthTypeForm(String value) {
divContent.removeAll();
switch(value) {
case BASIC_AUTH:
divContent.add(basicForm);
break;
case OAUTH2:
divContent.add(oauthForm);
break;
}
}

/**
* Validation method for the authorization type select box, and the fields in the corresponding form
* @param authType : String corresponding to the Authorization Type chosen in the select box
* @return true if the form is valid, false otherwise
*/
private boolean validateFields(String authType) {
// Mark the authorization type select box as invalid if a value is not selected
if (authTypeSelect.isEmpty()) {
authTypeSelect.setInvalid(true);
return false; // set the validation as failed
}
// Once the authorization type is chosen, ensure that the proper fields are filled for the generation
switch (authType) {
case BASIC_AUTH:
basicUsername.setInvalid(basicUsername.isEmpty());
basicPassword.setInvalid(basicPassword.isEmpty());
return !(basicUsername.isEmpty() || basicPassword.isEmpty());
case OAUTH2:
oauthToken.setInvalid(oauthToken.isEmpty());
return !(oauthToken.isEmpty());
}
return false;
}

private void buildForms() {
buildBasicAuthForm();
buildOAuth2Form();
}

/**
* Create the elements necessary to render the Basic Auth form using the basicForm instance
*/
private void buildBasicAuthForm() {
basicForm = new FormLayout();
basicForm.setWidthFull();

basicUsername = new TextField();
basicUsername.setWidth(FIELD_WIDTH);
basicUsername.setRequiredIndicatorVisible(true);
basicUsername.setErrorMessage("This field is required");
basicUsername.setRequired(true);
basicUsername.setLabel("Username");

basicPassword = new TextField();
basicPassword.setWidth(FIELD_WIDTH);
basicPassword.setRequiredIndicatorVisible(true);
basicPassword.setErrorMessage("This field is required");
basicPassword.setRequired(true);
basicPassword.setLabel("Password");

basicForm.add(basicUsername);
basicForm.add(basicPassword);
}

/**
* Create the elements necessary to render the OAuth 2 form using the oauthForm instance
*/
private void buildOAuth2Form() {
oauthForm = new FormLayout();
oauthForm.setWidthFull();

oauthToken = new TextField();
oauthToken.setWidth(FIELD_WIDTH);
oauthToken.setRequiredIndicatorVisible(true);
oauthToken.setErrorMessage("This field is required");
oauthToken.setLabel("OAuth 2 Token");

oauthForm.add(oauthToken);
}

/**
* Generate the Authorization Header based on the Authorization type and the data entered.
* The headers are appended to the parent form headers field.
* @param authType : String corresponding to the Authorization Type chosen in the select box
*/
private void generateAuthHeaders(String authType) {
StringBuilder sb = new StringBuilder();
sb.append(AUTHORIZATION_TAG);
sb.append("\n");
sb.append("<value>");
switch(authType) {
case BASIC_AUTH:
String credentials = basicUsername.getValue() + ":" + basicPassword.getValue();
sb.append("Basic ");
sb.append(Base64.getEncoder().encodeToString(credentials.getBytes()));
break;
case OAUTH2:
sb.append("Bearer ");
sb.append(oauthToken.getValue());
break;
}
sb.append("</value>");

this.parentForm.appendToHeaders(sb.toString());
close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
*/
package org.karnak.frontend.forwardnode.edit.destination.component;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.dom.Style;
import org.apache.commons.lang3.StringUtils;
import org.karnak.backend.data.entity.DestinationEntity;
import org.karnak.frontend.component.BoxShadowComponent;
import org.karnak.frontend.extid.WarningDialog;
import org.karnak.frontend.forwardnode.edit.component.ButtonSaveDeleteCancel;
import org.karnak.frontend.kheops.SwitchingAlbumsView;
import org.karnak.frontend.util.UIS;
Expand All @@ -34,7 +37,7 @@ public class FormSTOW extends VerticalLayout {

private TextField url;

private TextField urlCredentials;
private Button generateAuthorizationHeaderButton;

private TextArea headers;

Expand Down Expand Up @@ -69,20 +72,20 @@ public void init(Binder<DestinationEntity> binder, ButtonSaveDeleteCancel button
this.tagMorphingComponent.init(this.binder);
this.filterBySOPClassesForm.init(this.binder);
this.destinationCondition.init(binder);
notificationComponent.init(binder);
transferSyntaxComponent.init(this.binder);
transcodeOnlyUncompressedComponent.init(this.binder);
this.notificationComponent.init(binder);
this.transferSyntaxComponent.init(this.binder);
this.transcodeOnlyUncompressedComponent.init(this.binder);

this.description = new TextField("Description");
this.url = new TextField("URL");
this.urlCredentials = new TextField("URL credentials");
this.generateAuthorizationHeaderButton = new Button(AuthHeadersGenerationDialog.TITLE);
this.headers = new TextArea("Headers");
this.switchingAlbumsView = new SwitchingAlbumsView();
this.activate = new Checkbox("Enable destination");

// Define layout
VerticalLayout destinationLayout = new VerticalLayout(UIS.setWidthFull(new HorizontalLayout(description)),
destinationCondition, UIS.setWidthFull(new HorizontalLayout(url, urlCredentials)),
destinationCondition, UIS.setWidthFull(new HorizontalLayout(url, generateAuthorizationHeaderButton)),
UIS.setWidthFull(headers));
VerticalLayout transferLayout = new VerticalLayout(
new HorizontalLayout(transferSyntaxComponent, transcodeOnlyUncompressedComponent));
Expand All @@ -108,19 +111,33 @@ public void init(Binder<DestinationEntity> binder, ButtonSaveDeleteCancel button

setElements();
setBinder();
configureGenerateHeadersButton();
}

private void configureGenerateHeadersButton() {
this.generateAuthorizationHeaderButton.addClickListener(e -> {
if (this.headers.getValue().contains(AuthHeadersGenerationDialog.AUTHORIZATION_TAG)) {
WarningDialog wd = new WarningDialog("Cannot generate Authorization Header", "The Headers already contain an Authorization tag. Please remove it if you want to generate it.", "Ok");
wd.open();
} else {
AuthHeadersGenerationDialog dialog = new AuthHeadersGenerationDialog(this);
dialog.open();
}
});
}

private void setElements() {
description.setWidth("100%");

url.setWidth("50%");
url.setWidth("70%");
UIS.setTooltip(url, "The destination STOW-RS URL");

urlCredentials.setWidth("50%");
UIS.setTooltip(urlCredentials, "Credentials of the STOW-RS service (format is \"user:password\")");
generateAuthorizationHeaderButton.setWidth("30%");
generateAuthorizationHeaderButton.getStyle().setAlignSelf(Style.AlignSelf.FLEX_END);

headers.setMinHeight("10em");
headers.setWidth("100%");
headers.getStyle().set("padding", "0px");
UIS.setTooltip(headers,
"Headers for HTTP request. Example of format:\n<key>Authorization</key>\n<value>Bearer 1v1pwxT4Ww4DCFzyaMt0NP</value>");
}
Expand All @@ -136,6 +153,14 @@ private void setBinder() {
binder.bindInstanceFields(this);
}

public void appendToHeaders(String value) {
String existingHeaders = this.headers.getValue();
if (!existingHeaders.isEmpty()) {
existingHeaders += "\n";
}
this.headers.setValue(existingHeaders + value);
}

public DeIdentificationComponent getDeIdentificationComponent() {
return deIdentificationComponent;
}
Expand Down
Loading

0 comments on commit c0b1a77

Please sign in to comment.