blob: 8e006d10610b760e19eb191ac177e39d0801bada [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.openaz.xacml.admin.components;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.Iterator;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySetType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
import org.vaadin.dialogs.ConfirmDialog;
import org.vaadin.dialogs.ConfirmDialog.ContentMode;
import org.apache.openaz.xacml.admin.XacmlAdminAuthorization;
import org.apache.openaz.xacml.admin.XacmlAdminUI;
import org.apache.openaz.xacml.admin.model.GitRepositoryContainer;
import org.apache.openaz.xacml.admin.util.AdminNotification;
import org.apache.openaz.xacml.admin.util.OnDemandFileDownloader;
import org.apache.openaz.xacml.admin.util.OnDemandFileDownloader.OnDemandStreamResource;
import org.apache.openaz.xacml.admin.util.XACMLPolicyImporter;
import org.apache.openaz.xacml.admin.view.windows.GitPushWindow;
import org.apache.openaz.xacml.admin.view.windows.GitSynchronizeWindow;
import org.apache.openaz.xacml.admin.view.windows.PolicyNameEditorWindow;
import org.apache.openaz.xacml.admin.view.windows.PolicyUploadWindow;
import org.apache.openaz.xacml.admin.view.windows.RenamePolicyFileWindow;
import org.apache.openaz.xacml.admin.view.windows.SubDomainEditorWindow;
import org.apache.openaz.xacml.std.pap.StdPDPPolicy;
import org.apache.openaz.xacml.util.XACMLPolicyScanner;
import org.apache.openaz.xacml.util.XACMLPolicyScanner.CallbackResult;
import org.apache.openaz.xacml.util.XACMLPolicyWriter;
import com.vaadin.annotations.AutoGenerated;
import com.vaadin.data.Item;
import com.vaadin.event.Action;
import com.vaadin.event.Action.Handler;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.Transferable;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.acceptcriteria.AcceptAll;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.ui.AbstractSelect.AbstractSelectTargetDetails;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.HorizontalSplitPanel;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.TabSheet.CloseHandler;
import com.vaadin.ui.TabSheet.Tab;
import com.vaadin.ui.Table.TableDragMode;
import com.vaadin.ui.Table.TableTransferable;
import com.vaadin.ui.TreeTable;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window.CloseEvent;
import com.vaadin.ui.Window.CloseListener;
/**
* The class represents Policy Editor in Policy Authoring Tool
*/
public class PolicyWorkspace extends CustomComponent implements DropHandler, OnDemandStreamResource {
/*- VaadinEditorProperties={"grid":"RegularGrid,20","showGrid":true,"snapToGrid":true,"snapToObject":true,"movingGuides":false,"snappingDistance":10} */
@AutoGenerated
private VerticalLayout mainLayout;
@AutoGenerated
private HorizontalSplitPanel horizontalSplitPanel;
@AutoGenerated
private VerticalLayout verticalLayoutRightPanel;
@AutoGenerated
private TabSheet tabSheet;
@AutoGenerated
private HorizontalLayout horizontalLayoutRightToolbar;
@AutoGenerated
private Button buttonRight;
@AutoGenerated
private VerticalLayout verticalLayoutLeftPanel;
@AutoGenerated
private TreeTable treeWorkspace;
@AutoGenerated
private HorizontalLayout horizontalLayoutLeftToolbar;
@AutoGenerated
private Button buttonLeft;
@AutoGenerated
private HorizontalLayout horizontalLayoutLeftToolbarLeft;
@AutoGenerated
private Button buttonExport;
@AutoGenerated
private Button buttonSynchronize;
private static final long serialVersionUID = 1L;
private static final Log logger = LogFactory.getLog(PolicyWorkspace.class);
private final PolicyWorkspace self = this;
private final OnDemandFileDownloader downloader = new OnDemandFileDownloader(this);
private GitRepositoryContainer treeContainer;
private static final Action EXPORT_REPOSITORY = new Action ("Export Repository");
private static final Action SYNCHRONIZE_REPOSITORY =new Action ("Synchronize");
private static final Action PUSH_CHANGES = new Action ("Push Changes");
private static final Action CREATE_SUBDOMAIN = new Action("Create Sub Domain");
private static final Action RENAME_SUBDOMAIN = new Action("Rename Sub Domain");
private static final Action CREATE_NEWPOLICY = new Action ("Create New Policy");
private static final Action RENAME_POLICY = new Action ("Rename Policy");
private static final Action IMPORT_POLICY = new Action ("Import Policy");
private static final Action DELETE_SUBDOMAIN = new Action ("Delete Sub Domain");
private static final Action CLONE_POLICY = new Action ("Clone Policy");
private static final Action VIEW_POLICY = new Action ("View Policy");
private static final Action EDIT_POLICY = new Action ("Edit Policy");
private static final Action EXPORT_POLICY = new Action ("Export Policy");
private static final Action DELETE_POLICY = new Action ("Delete Policy");
public static final String VIEWNAME = PolicyWorkspace.class.getCanonicalName();
/**
* The constructor should first build the main layout, set the
* composition root and then do any custom initialization.
*
* The constructor will not be automatically regenerated by the
* visual editor.
*/
public PolicyWorkspace() {
buildMainLayout();
setCompositionRoot(mainLayout);
//
// Initialize GUI
//
this.initializeTree();
this.initializeButtons();
}
protected void initializeButtons() {
buttonLeft.addClickListener(new ClickListener() {
private static final long serialVersionUID = 1L;
@Override
public void buttonClick(ClickEvent event) {
if (horizontalSplitPanel.getSplitPosition() == 100.0)
horizontalSplitPanel.setSplitPosition(36, Unit.PERCENTAGE);
else
horizontalSplitPanel.setSplitPosition(0);
}
});
buttonRight.addClickListener(new ClickListener() {
private static final long serialVersionUID = 1L;
@Override
public void buttonClick(ClickEvent event) {
if (horizontalSplitPanel.getSplitPosition() == 0.0)
horizontalSplitPanel.setSplitPosition(36, Unit.PERCENTAGE);
else
horizontalSplitPanel.setSplitPosition(100, Unit.PERCENTAGE);
}
});
//
// Check user write-access
//
if (((XacmlAdminUI)UI.getCurrent()).isAuthorized(
XacmlAdminAuthorization.AdminAction.ACTION_WRITE,
XacmlAdminAuthorization.AdminResource.RESOURCE_POLICY_WORKSPACE)) {
this.buttonSynchronize.addClickListener(new ClickListener() {
private static final long serialVersionUID = 1L;
@Override
public void buttonClick(ClickEvent event) {
self.synchronizeRepository();
}
});
} else {
logger.info("user not authorized to write, removing synchronize button.");
this.buttonSynchronize.setVisible(false);
}
//
// The export button is attached to dynamic downloader
//
downloader.extend(this.buttonExport);
}
protected void initializeTree() {
//
// This is where the user's Git repository is located
//
final Path gitPath = ((XacmlAdminUI)UI.getCurrent()).getUserGitPath();
//
// Create our Git file system container
//
this.treeContainer = new GitRepositoryContainer(gitPath, gitPath.toFile());
//
// Create our own filter to filter out File extensions and
// also the Git directory.
//
this.treeContainer.setFilter(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//
// We don't want any of the hidden files
//
if (name.startsWith(".git") || name.equals(".DS_Store")) {
return false;
}
//
// We definitely want xml files
//
if (name.endsWith(".xml")) {
return true;
}
//
// We should test if its a directory, we want those
// included.
//
Path path = Paths.get(dir.getAbsolutePath(), name);
if (Files.isDirectory(path)) {
return true;
}
logger.warn("Filtering out: " + path.toString());
return false;
}
});
//
// Set TreeTables datasource as our git container
//
this.treeWorkspace.setContainerDataSource(this.treeContainer);
//
// Setup other properties etc.
//
this.treeWorkspace.setItemIconPropertyId("Icon");
this.treeWorkspace.setVisibleColumns(new Object[]{"Name", "Version", "Size", "Last Modified", "Status"});
this.treeWorkspace.setSizeFull();
this.treeWorkspace.setSelectable(true);
this.treeWorkspace.setEditable(false);
//
// Expand the first couple of directories
//
for (Object id : this.treeWorkspace.getItemIds()) {
this.treeWorkspace.setCollapsed(id, false);
for (Object child : this.treeWorkspace.getChildren(id)) {
this.treeWorkspace.setCollapsed(child, false);
}
}
//
// Respond to table selections
//
/*
this.treeWorkspace.addValueChangeListener(new ValueChangeListener() {
private static final long serialVersionUID = 1L;
@Override
public void valueChange(ValueChangeEvent event) {
File selection = (File) self.treeWorkspace.getValue();
if (selection != null) {
self.buttonImport.setEnabled(selection.isDirectory());
self.buttonExport.setEnabled(selection.isFile());
} else {
}
}
});
*/
this.treeWorkspace.addItemClickListener(new ItemClickListener() {
private static final long serialVersionUID = 1L;
@Override
public void itemClick(ItemClickEvent event) {
if (event.isDoubleClick() &&
event.getItemId() instanceof File &&
((File) event.getItemId()).isFile()) {
self.openPolicyTab((File) event.getItemId(), true);
}
}
});
//
// Setup our action handlers
//
this.treeWorkspace.addActionHandler(new Handler() {
private static final long serialVersionUID = 1L;
@Override
public Action[] getActions(Object target, Object sender) {
if (target == null) {
//
// Nothing is selected, they right-clicked empty space
//
return new Action[] {SYNCHRONIZE_REPOSITORY, EXPORT_REPOSITORY, CREATE_SUBDOMAIN};
}
if (! (target instanceof File)) {
return null;
}
if (((File)target).isDirectory()) {
//
// Selected a directory
//
return new Action[] {CREATE_SUBDOMAIN, RENAME_SUBDOMAIN, DELETE_SUBDOMAIN, CREATE_NEWPOLICY, IMPORT_POLICY, PUSH_CHANGES};
}
if (((File)target).isFile()) {
//
// Selected a policy file
//
return new Action[] {VIEW_POLICY, EDIT_POLICY, CLONE_POLICY, EXPORT_POLICY, RENAME_POLICY, DELETE_POLICY, PUSH_CHANGES};
}
return null;
}
@Override
public void handleAction(Action action, Object sender, Object target) {
if (action == SYNCHRONIZE_REPOSITORY) {
self.synchronizeRepository();
return;
}
if (action == EXPORT_REPOSITORY) {
self.exportRepository();
return;
}
if (action == PUSH_CHANGES) {
self.pushChanges((File) target);
return;
}
if (action == CREATE_SUBDOMAIN) {
self.editSubDomain((File) target, null);
return;
}
if (action == RENAME_SUBDOMAIN) {
self.editSubDomain((File) self.treeWorkspace.getParent(target), ((File)target).getName());
return;
}
if (action == CREATE_NEWPOLICY) {
self.createPolicy((File) target);
return;
}
if (action == RENAME_POLICY) {
self.renamePolicy((File) target);
return;
}
if (action == IMPORT_POLICY) {
self.importPolicy((File) target);
return;
}
if (action == DELETE_SUBDOMAIN) {
self.deleteSubDomain((File) target);
return;
}
if (action == CLONE_POLICY) {
self.clonePolicy((File) target);
return;
}
if (action == VIEW_POLICY) {
self.openPolicyTab((File) target, true);
return;
}
if (action == EDIT_POLICY) {
self.openPolicyTab((File) target, false);
return;
}
if (action == EXPORT_POLICY) {
return;
}
if (action == DELETE_POLICY) {
self.deletePolicy((File) target);
return;
}
}
});
//
// Set the drop handler
//
this.treeWorkspace.setDragMode(TableDragMode.ROW);
this.treeWorkspace.setDropHandler(this);
//
// Detect when a tab closes and remove it from the
// tab sheet.
//
this.tabSheet.setCloseHandler(new CloseHandler() {
private static final long serialVersionUID = 1L;
@Override
public void onTabClose(TabSheet tabsheet, Component tabContent) {
logger.info("tabsheet closing: " + tabsheet.getCaption());
tabsheet.removeTab(tabsheet.getTab(tabContent));
}
});
}
protected void editSubDomain(final File parent, final String subdomain) {
final SubDomainEditorWindow editor = new SubDomainEditorWindow(null);
editor.setCaption((subdomain == null ? "Create New SubDomain" : "Edit SubDomain"));
editor.setCloseShortcut(KeyCode.ESCAPE);
editor.setModal(true);
editor.addCloseListener(new CloseListener() {
private static final long serialVersionUID = 1L;
@Override
public void windowClose(CloseEvent event) {
//
// Did the user save it
//
if (editor.isSaved() == false) {
return;
}
String newSubDomain = editor.getSubDomain();
if (newSubDomain == null) {
logger.warn("Shouldn't have a null subdomain if the user clicked save button");
return;
}
//
// New subdomain?
//
if (subdomain == null) {
//
// Create new one
//
Path createDir;
if (parent == null) {
//
// New Root domain
//
createDir = Paths.get(((XacmlAdminUI)UI.getCurrent()).getUserGitPath().toAbsolutePath().toString(), newSubDomain);
} else {
//
// New subdomain
//
createDir = Paths.get(parent.getAbsolutePath(), newSubDomain);
}
try {
//
// Does the new subdomain exist?
//
Path newDir;
if (Files.exists(createDir)) {
//
// It already exists
//
newDir = createDir;
} else {
//
// Create it
//
newDir = Files.createDirectory(createDir);
//
// Create empty .gitignore file
//
Files.createFile(Paths.get(newDir.toString(), ".gitignore"));
}
//
// Setup the TreeTable
//
File file = newDir.toFile();
if (parent == null) {
Item item = self.treeWorkspace.addItem(file);
if (item != null) {
self.treeWorkspace.setCollapsed(file, false);
self.treeWorkspace.select(file);
}
} else {
Item item = self.treeWorkspace.addItem(file);
if (item != null) {
self.treeWorkspace.setParent(file, parent);
self.treeWorkspace.setCollapsed(parent, false);
self.treeWorkspace.select(file);
}
}
} catch (IOException e) {
logger.error("Failed to create subdomain: " + createDir.toString(), e);
}
} else {
//
// Get our paths
//
Path oldDir = Paths.get(parent.getAbsolutePath(), subdomain);
Path newDir = Paths.get(parent.getAbsolutePath(), newSubDomain);
try {
//
// Rename the subdomain
//
Files.move(oldDir, newDir);
//
// Add to the TreeTable
//
File newFile = newDir.toFile();
File oldFile = oldDir.toFile();
Item item = self.treeWorkspace.addItem(newFile);
if (item != null) {
self.treeWorkspace.setChildrenAllowed(newFile, true);
//
// Make sure its parent is the same as the old one, unless they
// renamed the top-level.
//
Object parent = self.treeWorkspace.getParent(oldFile);
if (parent != null) {
self.treeWorkspace.setParent(newFile, parent);
}
//
// Make any children of the old subdomain now children
// of the new subdomain.
//
Collection<?> children = self.treeWorkspace.getChildren(oldFile);
Iterator<?> iter = children.iterator();
while (iter.hasNext()) {
Object child = iter.next();
self.treeWorkspace.setParent(child, newFile);
}
//
// Finally remove the old subdomain
//
self.treeWorkspace.removeItem(oldFile);
}
} catch (IOException e) {
logger.error("Failed to rename subdomain: " + oldDir.toString() + " to: " + newDir.toString(), e);
}
}
}
});
editor.center();
UI.getCurrent().addWindow(editor);
}
protected void deleteSubDomain(final File subdomain) {
String message = "Are you sure you want to delete subdomain\n" + subdomain.getName() + "\nThis will remove <B>ALL</B> of its subdomains and policy files.";
ConfirmDialog dialog = ConfirmDialog.getFactory().create("Confirm SubDomain Deletion", message, "Delete", "Cancel");
dialog.setContentMode(ContentMode.HTML);
dialog.show(getUI(), new ConfirmDialog.Listener() {
private static final long serialVersionUID = 1L;
@Override
public void onClose(ConfirmDialog dialog) {
if (dialog.isConfirmed()) {
//
// Iterate the subdomain
//
try {
Files.walkFileTree(Paths.get(subdomain.getAbsolutePath()), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path deleteFile, BasicFileAttributes attrs)
throws IOException {
try {
boolean removeFromTree = deleteFile.getFileName().toString().endsWith(".xml");
Files.delete(deleteFile);
if (removeFromTree) {
self.treeWorkspace.removeItem(deleteFile.toFile());
}
if (logger.isDebugEnabled()) {
logger.debug("Deleted file: " + deleteFile.toString());
}
} catch (IOException e) {
logger.error("Failed to delete file: " + deleteFile.toString(), e);
return FileVisitResult.TERMINATE;
}
return super.visitFile(deleteFile, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
try {
Files.delete(dir);
self.treeWorkspace.removeItem(dir.toFile());
if (logger.isDebugEnabled()) {
logger.debug("Deleted dir: " + dir.toString());
}
} catch (IOException e) {
logger.error("Failed to delete directory: " + dir.toString(), e);
return FileVisitResult.TERMINATE;
}
return super.postVisitDirectory(dir, exc);
}
});
} catch (IOException e) {
logger.error("Failed to walk subdomain: " + subdomain.getAbsolutePath(), e);
}
}
}
}, true);
}
protected void createPolicy(final File parentDirectory) {
//
// Construct our parameters
//
Path parent = Paths.get(parentDirectory.getAbsolutePath());
Path newFile = this.getNextFilename(parent, "Policy");
//
// Run the window
//
this.runPolicyWindow("Create New Policy", parent, newFile, null, null);
}
protected void renamePolicy(final File policy) {
//
// Run the rename window
//
final RenamePolicyFileWindow window = new RenamePolicyFileWindow(policy.getName());
window.setCaption("Rename Policy");
window.setModal(true);
window.addCloseListener(new CloseListener() {
private static final long serialVersionUID = 1L;
@Override
public void windowClose(CloseEvent event) {
String newFilename = window.getNewFilename();
if (newFilename == null) {
//
// User cancelled
//
return;
}
Path newPolicy = Paths.get(policy.getParent(), newFilename);
if (Files.exists(newPolicy)) {
Notification.show("Cannot rename to an existing file", Notification.Type.ERROR_MESSAGE);
return;
}
try {
if (policy.renameTo(newPolicy.toFile()) == false) {
throw new Exception("No known error, rename failed");
}
self.treeContainer.updateItem(newPolicy.getParent().toFile());
} catch (Exception e) {
Notification.show("Failed to rename file: " + e.getLocalizedMessage());
}
}
});
window.center();
UI.getCurrent().addWindow(window);
}
protected void clonePolicy(final File policy) {
//
// Figure out a new name for the cloned policy
//
Path policyClone = Paths.get(policy.getAbsolutePath());
Path newFile = this.getNextFilename(policyClone.getParent(), policy.getName());
if (newFile == null) {
return;
}
//
// Scan the policy in, replace policy ID's and rule ID's
//
Object policyData = new XACMLPolicyScanner(policyClone, new XACMLPolicyScanner.SimpleCallback() {
@Override
public CallbackResult onPreVisitRule(PolicyType parent, RuleType rule) {
rule.setRuleId(((XacmlAdminUI)getUI()).newRuleID());
return CallbackResult.CONTINUE;
}
@Override
public CallbackResult onPreVisitPolicySet(PolicySetType parent, PolicySetType policySet) {
policySet.setPolicySetId(((XacmlAdminUI)getUI()).newPolicyID());
policySet.setVersion("1");
return CallbackResult.CONTINUE;
}
@Override
public CallbackResult onPreVisitPolicy(PolicySetType parent, PolicyType policy) {
policy.setPolicyId(((XacmlAdminUI)getUI()).newPolicyID());
policy.setVersion("1");
return CallbackResult.CONTINUE;
}
}).scan();
//
// Run the window
//
this.runPolicyWindow("Clone Policy", newFile.getParent(), newFile.getFileName(), policyData, null);
}
protected void runPolicyWindow(String caption, final Path parentPath, final Path policyPath, final Object policyData, final Path oldPolicyFile) {
//
// Create our editor window
//
final PolicyNameEditorWindow editor = new PolicyNameEditorWindow((policyPath != null ? policyPath.getFileName().toString() : null),
policyData,
((XacmlAdminUI)getUI()).getPolicyAlgorithms(),
((XacmlAdminUI)getUI()).getRuleAlgorithms());
editor.setCaption(caption);
editor.setCloseShortcut(KeyCode.ESCAPE);
editor.setModal(true);
editor.addCloseListener(new CloseListener() {
private static final long serialVersionUID = 1L;
@Override
public void windowClose(CloseEvent event) {
//
// Did the user hit save button or esc?
//
if (editor.isSaved() == false) {
return;
}
final Object data = editor.getPolicyData();
String filename = editor.getPolicyFilename();
if (filename == null || data == null) {
logger.warn("Editor said is was saved but filename/data is null.");
return;
}
//
// Determine new path
//
final Path newPolicyPath = Paths.get(parentPath.toString(), filename);
//
// Is it ok to overwrite the new file?
//
try {
//
// Handle if we are not replacing an old file and we are overwriting
// an existing file.
//
if (self.isOverwritingAPolicy(newPolicyPath, oldPolicyFile) == false && Files.exists(newPolicyPath)) {
//
// Confirm they wanted to do this, and figure out
// new version number.
//
String message = "You are overwriting a file: " + newPolicyPath.getFileName().toString();
ConfirmDialog dialog = ConfirmDialog.getFactory().create("Confirm Policy File Overwriting", message, "Overwrite", "Cancel");
dialog.setData(false);
dialog.setContentMode(ContentMode.HTML);
dialog.setModal(true);
dialog.show(getUI(), new ConfirmDialog.Listener() {
private static final long serialVersionUID = 1L;
@Override
public void onClose(ConfirmDialog dialog) {
if (dialog.isConfirmed()) {
//
// Yep the user wants to overwrite it
//
self.savePolicy(newPolicyPath, data, oldPolicyFile);
//
// Open it for editing
//
self.openPolicyTab(newPolicyPath.toFile(), false);
}
}
}, true);
//
// Exit out of this thread
//
return;
}
} catch (Exception e) {
logger.error(e);
return;
}
//
// Save it off
//
self.savePolicy(newPolicyPath, data, oldPolicyFile);
//
// Open it for editing
//
self.openPolicyTab(newPolicyPath.toFile(), false);
}
});
editor.center();
UI.getCurrent().addWindow(editor);
}
protected Path getNextFilename(Path parent, String filename) {
filename = FilenameUtils.removeExtension(filename);
Path newFile = null;
int i = 0;
while (true) {
newFile = Paths.get(parent.toString(), String.format("%s(%02d)", filename, i++) + ".xml");
if (Files.notExists(newFile)) {
return newFile;
}
if (i == 100) {
logger.error("Could not construct a new name for cloned policy.");
return null;
}
}
}
protected boolean isOverwritingAPolicy(Path newPolicyPath, Path oldPolicyPath) throws Exception {
//
// Check to see if we were editing an existing file. Then check if the
// new file actually exists. Then check if we are overwriting the original old file
//
if (oldPolicyPath != null && Files.exists(newPolicyPath) && Files.isSameFile(newPolicyPath, oldPolicyPath)) {
//
// Yes its the same, overwriting it is expected.
//
logger.info("isOverwritingAPolicy");
return true;
}
return false;
}
protected void savePolicy(final Path newPolicyPath, final Object policyData, Path oldPolicyPath) {
//
// Are they overwriting another policy?
//
String version = "1.0";
boolean delete = false;
if (oldPolicyPath != null) {
//
// This policy name was being edited. Is it still the same?
//
try {
delete = true;
if (Files.exists(newPolicyPath) && Files.isSameFile(newPolicyPath, oldPolicyPath)) {
delete = false;
}
} catch (Exception e) {
logger.error("Could not determine if same file", e);
return;
}
logger.info("Deleting old file: " + delete);
}
//
// Are we now overwriting another file?
//
if (Files.exists(newPolicyPath)) {
//
// Yes
//
logger.info("Overwriting file");
//
// Overwrite is happening. Bump the version (IF WE CAN)
//
//TODO - What if user wants to change something other than the last number? For example, changing 1.5.23 to 2.0.0.
//TODO We need a mechanism that allows the user to specify the new policy version (disallowing backtracking) if they desire
//TODO and get that new number (if any) passed down to here. This code then becomes the "then" branch of "If new version has been specified..."
try {
int[] versionArray = StdPDPPolicy.versionStringToArray(XACMLPolicyScanner.getVersion(newPolicyPath));
// increment the right-most digit
versionArray[versionArray.length - 1]++;
version = StdPDPPolicy.versionArrayToString(versionArray);
} catch (NumberFormatException | IOException e) {
try {
logger.warn("Previous version '" + XACMLPolicyScanner.getVersion(newPolicyPath) + "' not a series of itegers");
} catch (IOException e1) {
logger.error("could not get previous version");
}
//TODO - This may not be wise since the intent is to increase the version number. Perhaps we should abort this an go back to the user?
version = "1.0";
}
if (policyData instanceof PolicySetType) {
((PolicySetType) policyData).setVersion(version);
} else if (policyData instanceof PolicyType) {
((PolicyType) policyData).setVersion(version);
}
} else {
//
// Nope, a completely new file
//
logger.info("New file");
}
//
// Is the root a PolicySet or Policy?
//
Path finalPolicyPath;
if (policyData instanceof PolicySetType) {
//
// Write it out
//
finalPolicyPath = XACMLPolicyWriter.writePolicyFile(newPolicyPath, (PolicySetType) policyData);
} else if (policyData instanceof PolicyType) {
//
// Write it out
//
finalPolicyPath = XACMLPolicyWriter.writePolicyFile(newPolicyPath, (PolicyType) policyData);
} else {
logger.error("Unknown data type sent back.");
return;
}
//
// Did it get written?
//
if (finalPolicyPath == null || ! Files.exists(finalPolicyPath)) {
logger.error("Failed to write policy file.");
return;
}
//
// Add it into our tree
//
this.addPolicyFileToTree(finalPolicyPath.getParent().toFile(), finalPolicyPath.toFile());
//
// Do we need to delete the old file?
//
if (oldPolicyPath != null && delete) {
try {
Files.delete(oldPolicyPath);
} catch (Exception e) {
logger.error("Failed to delete old policy", e);
}
if (self.treeWorkspace.removeItem(oldPolicyPath.toFile()) == false) {
logger.warn("Failed to remove old policy path");
}
}
}
protected void deletePolicy(final File policy) {
String message = "Are you sure you want to delete policy: " + policy.getName();
ConfirmDialog dialog = ConfirmDialog.getFactory().create("Confirm Policy File Deletion", message, "Delete", "Cancel");
dialog.setContentMode(ContentMode.HTML);
dialog.show(getUI(), new ConfirmDialog.Listener() {
private static final long serialVersionUID = 1L;
@Override
public void onClose(ConfirmDialog dialog) {
if (dialog.isConfirmed() && policy.delete()) {
self.treeWorkspace.removeItem(policy);
if (logger.isDebugEnabled()) {
logger.debug("Deleted file: " + policy.toString());
}
}
}
}, true);
}
protected void importPolicy(final File domain) {
//
// Get the current domain
//
if (! domain.isDirectory()) {
logger.error("Table must have a directory selected to import the file.");
return;
}
//
// Create the upload window
//
final PolicyUploadWindow upload = new PolicyUploadWindow(Paths.get(domain.toURI()));
upload.setCaption("Import Xacml 3.0 Policy File");
upload.setCloseShortcut(KeyCode.ESCAPE);
upload.setModal(true);
upload.addCloseListener(new CloseListener() {
private static final long serialVersionUID = 1L;
@Override
public void windowClose(CloseEvent e) {
//
// Was it successful?
//
Path newFile = upload.getUploadedFile();
if (newFile == null) {
return;
}
//
// Add it
//
self.addPolicyFileToTree(domain, newFile.toFile());
//
// Are we importing anything in the policy file?
//
boolean importAttributes = upload.importAttributes();
boolean importObligations = upload.importObligations();
boolean importAdvice = upload.importAdvice();
if (importAttributes || importObligations || importAdvice) {
//
// Create our importer
//
XACMLPolicyImporter importer = new XACMLPolicyImporter();
importer.setImportAttributes(importAttributes);
importer.setImportObligations(importObligations);
importer.setImportAdvice(importAdvice);
importer.setIgnoreStandardAttributes(upload.ignoreStandard());
//
// Yes load and scan the policy
//
new XACMLPolicyScanner(newFile, importer).scan();
}
}
});
upload.center();
UI.getCurrent().addWindow(upload);
}
protected void addPolicyFileToTree(File domain, File file) {
//
// Add it into our tree
//
if (this.treeWorkspace.addItem(file) != null) {
//
// Make sure it has the right parent
//
this.treeWorkspace.setParent(file, domain);
//
// Select our new policy
//
self.treeWorkspace.select(file);
} else {
logger.error("Failed to add policy to workspace tree");
}
}
/*
protected void publishPolicy(final File policy) {
//
// Get its ID
//
Item item = this.treeContainer.getItem(policy);
if (item == null) {
logger.error("Failed to get the item");
return;
}
Object policyData = item.getItemProperty("Data");
if (policyData == null) {
logger.error("Failed to get item data property.");
return;
}
String fullId = XACMLPolicyScanner.getID(policyData);
String version = XACMLPolicyScanner.getVersion(policyData);
if (fullId == null || version == null) {
logger.error("Failed to get policy Id");
return;
}
List<String> ids = Lists.newArrayList(Splitter.on(':').split(fullId));
if (ids.isEmpty()) {
logger.error("Couldn't parse policy Id");
return;
}
final String id = ids.get(ids.size() - 1) + "." + version;
//
// Is there only one group?
//
PAPEngine engine = ((XacmlAdminUI)getUI()).getPAPEngine();
Set<PDPGroup> groups;
PDPGroup defaultGroup;
try {
groups = engine.getPDPGroups();
} catch (PAPException e) {
String message = "Unable to retrieve Groups from server: " + e;
logger.error(message, e);
throw new RuntimeException(message);
}
try {
defaultGroup = engine.getDefaultGroup();
} catch (PAPException e) {
String message = "Unable to retrieve Default Group from server: " + e;
logger.error(message, e);
throw new RuntimeException(message);
}
if (groups.size() == 1) {
this.doPublish(id, policy, defaultGroup);
return;
}
//
// Have user select a group
//
final SelectPDPGroupWindow window = new SelectPDPGroupWindow(groups, "Select PDP Group to publish in");
window.setCaption("Select PDP Group");
window.setCloseShortcut(KeyCode.ESCAPE);
window.setModal(true);
window.addCloseListener(new CloseListener() {
private static final long serialVersionUID = 1L;
@Override
public void windowClose(CloseEvent event) {
PDPGroup group = window.selectedGroup();
if (group != null) {
self.doPublish(id, policy, group);
}
}
});
window.center();
UI.getCurrent().addWindow(window);
}
protected void doPublish(String id, File policy, PDPGroup group) {
//
// The policy status must be up-to-date
//
// TODO
//
// TODO - get list of referenced policies and publish
// them first.
//
//
// Publish the policy
//
PAPEngine engine = ((XacmlAdminUI)getUI()).getPAPEngine();
try (InputStream is = new FileInputStream(policy)) {
engine.publishPolicy(id, policy.getName(), true, is, group);
} catch (PAPException | IOException e) {
logger.error("Failed to publish policy: ", e);
}
}
*/
protected void pushChanges(final File target) {
try {
//
// Grab our working repository
//
Path repoPath = ((XacmlAdminUI)getUI()).getUserGitPath();
final Git git = Git.open(repoPath.toFile());
//
// Get our status
//
final String base;
Status status;
if (target == null) {
base = ".";
} else {
Path relativePath = repoPath.relativize(Paths.get(target.getPath()));
base = relativePath.toString();
}
if (logger.isDebugEnabled()) {
logger.debug("Status on base: " + base);
}
status = git.status().addPath(base).call();
//
// Check if its clean
//
if (status.isClean()) {
//
// Its clean
//
AdminNotification.warn(target.getName() + " is clean!");
return;
}
//
// Create the window
//
final GitPushWindow window = new GitPushWindow(git, target, status);
window.setCaption("Push Changes");
window.setModal(true);
window.addCloseListener(new CloseListener() {
private static final long serialVersionUID = 1L;
@Override
public void windowClose(CloseEvent e) {
if (window.isSaved() == false) {
return;
}
try {
//
// Needs to be added first
//
DirCache cache = git.add().addFilepattern(base).call();
for (int i = 0; i < cache.getEntryCount(); i++) {
DirCacheEntry entry = cache.getEntry(i);
if (logger.isDebugEnabled()) {
logger.debug("Entry: " + entry);
}
}
//
// Next they need to be committed
//
RevCommit rev = git.commit().setMessage(window.getComment()).call();
if (logger.isDebugEnabled()) {
logger.debug("RevCommit: " + rev);
}
//
// Now we can push changes to the Git repository
//
Iterable<PushResult> results = git.push().call();
for (PushResult result : results) {
logger.info(result);
}
//
// Have the container fire an item set change notification
//
self.treeContainer.updateItem(target);
} catch (NoWorkTreeException | GitAPIException e1) {
logger.error(e);
AdminNotification.error("Exception occurred while trying to push: " + e1);
}
}
});
window.center();
UI.getCurrent().addWindow(window);
} catch (IOException | GitAPIException e) {
logger.error(e);
AdminNotification.error("Exception occurred while trying to get status: " + e);
}
}
protected void synchronizeRepository() {
final GitSynchronizeWindow window = new GitSynchronizeWindow();
window.setCaption("Synchronize with server repository?");
window.setModal(true);
window.center();
UI.getCurrent().addWindow(window);
}
protected void exportRepository() {
this.buttonExport.click();
}
@Override
public String getFilename() {
return "Repository.tgz";
}
@Override
public InputStream getStream() {
//
// Grab our working repository
//
final Path repoPath = ((XacmlAdminUI)getUI()).getUserGitPath();
Path workspacePath = ((XacmlAdminUI)getUI()).getUserWorkspace();
final Path tarFile = Paths.get(workspacePath.toString(), "Repository.tgz");
try (OutputStream os = Files.newOutputStream(tarFile)) {
try (GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(os)) {
try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzOut)) {
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
Files.walkFileTree(repoPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (dir.getFileName().toString().startsWith(".git")) {
return FileVisitResult.SKIP_SUBTREE;
}
Path relative = repoPath.relativize(dir);
if (relative.toString().isEmpty()) {
return super.preVisitDirectory(dir, attrs);
}
TarArchiveEntry entry = new TarArchiveEntry(relative.toFile());
tarOut.putArchiveEntry(entry);
tarOut.closeArchiveEntry();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().endsWith(".xml") == false) {
return super.visitFile(file, attrs);
}
Path relative = repoPath.relativize(file);
TarArchiveEntry entry = new TarArchiveEntry(relative.toFile());
entry.setSize(Files.size(file));
tarOut.putArchiveEntry(entry);
try {
IOUtils.copy(Files.newInputStream(file), tarOut);
} catch (IOException e) {
logger.error(e);
}
tarOut.closeArchiveEntry();
return super.visitFile(file, attrs);
}
});
tarOut.finish();
}
}
} catch (IOException e) {
logger.error(e);
}
try {
return Files.newInputStream(tarFile);
} catch (IOException e) {
logger.error(e);
}
return null;
}
protected void openPolicyTab(File policy, boolean readOnly) {
//
// Sanity check
//
assert policy != null;
assert policy.isFile();
if (policy == null || ! policy.isFile()) {
throw new IllegalArgumentException("You must specify a file.");
}
Status status;
Path relativePath;
String base;
try {
//
// Grab our working repository
//
Path repoPath = ((XacmlAdminUI)getUI()).getUserGitPath();
final Git git = Git.open(repoPath.toFile());
//
// Get our status
//
relativePath = repoPath.relativize(Paths.get(policy.getPath()));
base = relativePath.toString();
if (logger.isDebugEnabled()) {
logger.debug("Status on base: " + base);
}
status = git.status().addPath(base).call();
} catch (NoWorkTreeException | IOException | GitAPIException e) {
logger.error("Failed to get status on " + policy + " " + e);
AdminNotification.error("Could not get Git status on the file.");
return;
}
//
// Check if its clean
//
if (status.isClean() == false) {
//
// Check if its conflicting
//
for (String conflict : status.getConflicting()) {
if (conflict.equals(base)) {
//
// Yes - we won't be able to edit it
//
AdminNotification.error("Policy has conflicts with master, please synchronize the repository.");
return;
}
}
}
//
// Check to see if there already is a tab open
//
Iterator<Component> iter = self.tabSheet.iterator();
while (iter.hasNext()) {
Component c = iter.next();
if (c instanceof PolicyEditor) {
Object data = ((PolicyEditor) c).getData();
if (data != null && data instanceof File && ((File)data).equals(policy)) {
self.tabSheet.setSelectedTab(c);
return;
}
}
}
//
// No tab is open, create a new one
//
PolicyEditor editor = null;
try {
editor = new PolicyEditor(policy, this.treeContainer, readOnly);
} catch (IOException e) {
logger.error("Failed to open policy");
editor = null;
}
if (editor != null) {
editor.setWidth("100%");
Tab tab = self.tabSheet.addTab(editor);
editor.setTab(tab);
tab.setClosable(true);
self.tabSheet.setSelectedTab(editor);
} else {
AdminNotification.error("The Policy File is not a Xacml 3.0 policy.");
}
}
@Override
public void drop(DragAndDropEvent event) {
Transferable t = event.getTransferable();
Component source = t.getSourceComponent();
if (source != this.treeWorkspace) {
assert false;
throw new IllegalArgumentException();
}
TableTransferable tt = (TableTransferable) t;
File sourceFile = (File) tt.getItemId();
AbstractSelectTargetDetails target = (AbstractSelectTargetDetails)event.getTargetDetails();
File targetFile = (File) target.getItemIdOver();
if (sourceFile.isFile() && targetFile != null && targetFile.isDirectory()) {
//
// Construct destination filename
//
Path dest = targetFile.toPath().resolve(sourceFile.getName());
//
// Check if the target domain exists
//
if (Files.exists(dest)) {
//
// Prompt the user
//
Notification.show("A policy file with that name already exists in that directory.", Notification.Type.ERROR_MESSAGE);
} else {
//
// Go ahead and rename it
//
this.renamePolicyFile(sourceFile, dest.toFile(), targetFile);
}
}
}
protected void renamePolicyFile(File sourceFile, File dest, File parent) {
try {
if (sourceFile.renameTo(dest)) {
this.treeContainer.setParent(sourceFile, parent);
this.treeContainer.updateItem(parent);
}
} catch (Exception e) {
String error = "Failed to rename " + sourceFile + " to: " + dest + System.lineSeparator() + e.getLocalizedMessage();
logger.error(error);
AdminNotification.error(error);
}
}
@Override
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
@AutoGenerated
private VerticalLayout buildMainLayout() {
// common part: create layout
mainLayout = new VerticalLayout();
mainLayout.setImmediate(false);
mainLayout.setWidth("100%");
mainLayout.setHeight("100%");
mainLayout.setMargin(false);
// top-level component properties
setWidth("100.0%");
setHeight("100.0%");
// horizontalSplitPanel
horizontalSplitPanel = buildHorizontalSplitPanel();
mainLayout.addComponent(horizontalSplitPanel);
mainLayout.setExpandRatio(horizontalSplitPanel, 1.0f);
return mainLayout;
}
@AutoGenerated
private HorizontalSplitPanel buildHorizontalSplitPanel() {
// common part: create layout
horizontalSplitPanel = new HorizontalSplitPanel();
horizontalSplitPanel.setImmediate(false);
horizontalSplitPanel.setWidth("100.0%");
horizontalSplitPanel.setHeight("100.0%");
// verticalLayoutLeftPanel
verticalLayoutLeftPanel = buildVerticalLayoutLeftPanel();
horizontalSplitPanel.addComponent(verticalLayoutLeftPanel);
// verticalLayoutRightPanel
verticalLayoutRightPanel = buildVerticalLayoutRightPanel();
horizontalSplitPanel.addComponent(verticalLayoutRightPanel);
return horizontalSplitPanel;
}
@AutoGenerated
private VerticalLayout buildVerticalLayoutLeftPanel() {
// common part: create layout
verticalLayoutLeftPanel = new VerticalLayout();
verticalLayoutLeftPanel.setImmediate(false);
verticalLayoutLeftPanel.setWidth("100.0%");
verticalLayoutLeftPanel.setHeight("100.0%");
verticalLayoutLeftPanel.setMargin(true);
verticalLayoutLeftPanel.setSpacing(true);
// horizontalLayoutLeftToolbar
horizontalLayoutLeftToolbar = buildHorizontalLayoutLeftToolbar();
verticalLayoutLeftPanel.addComponent(horizontalLayoutLeftToolbar);
// treeWorkspace
treeWorkspace = new TreeTable();
treeWorkspace.setImmediate(true);
treeWorkspace.setWidth("100.0%");
treeWorkspace.setHeight("100.0%");
verticalLayoutLeftPanel.addComponent(treeWorkspace);
verticalLayoutLeftPanel.setExpandRatio(treeWorkspace, 1.0f);
return verticalLayoutLeftPanel;
}
@AutoGenerated
private HorizontalLayout buildHorizontalLayoutLeftToolbar() {
// common part: create layout
horizontalLayoutLeftToolbar = new HorizontalLayout();
horizontalLayoutLeftToolbar.setImmediate(false);
horizontalLayoutLeftToolbar.setWidth("100.0%");
horizontalLayoutLeftToolbar.setHeight("-1px");
horizontalLayoutLeftToolbar.setMargin(false);
horizontalLayoutLeftToolbar.setSpacing(true);
// horizontalLayoutLeftToolbarLeft
horizontalLayoutLeftToolbarLeft = buildHorizontalLayoutLeftToolbarLeft();
horizontalLayoutLeftToolbar
.addComponent(horizontalLayoutLeftToolbarLeft);
// buttonLeft
buttonLeft = new Button();
buttonLeft.setCaption("<<");
buttonLeft.setImmediate(true);
buttonLeft.setDescription("Minimize left panel.");
buttonLeft.setWidth("-1px");
buttonLeft.setHeight("-1px");
horizontalLayoutLeftToolbar.addComponent(buttonLeft);
horizontalLayoutLeftToolbar.setComponentAlignment(buttonLeft,
new Alignment(34));
return horizontalLayoutLeftToolbar;
}
@AutoGenerated
private HorizontalLayout buildHorizontalLayoutLeftToolbarLeft() {
// common part: create layout
horizontalLayoutLeftToolbarLeft = new HorizontalLayout();
horizontalLayoutLeftToolbarLeft.setImmediate(false);
horizontalLayoutLeftToolbarLeft.setWidth("-1px");
horizontalLayoutLeftToolbarLeft.setHeight("-1px");
horizontalLayoutLeftToolbarLeft.setMargin(false);
horizontalLayoutLeftToolbarLeft.setSpacing(true);
// buttonSynchronize
buttonSynchronize = new Button();
buttonSynchronize.setCaption("Synchronize Repository");
buttonSynchronize.setImmediate(true);
buttonSynchronize
.setDescription("Synchronize local repository with main branch.");
buttonSynchronize.setWidth("-1px");
buttonSynchronize.setHeight("-1px");
horizontalLayoutLeftToolbarLeft.addComponent(buttonSynchronize);
// buttonExport
buttonExport = new Button();
buttonExport.setCaption("Export Workspace");
buttonExport.setImmediate(true);
buttonExport.setDescription("Export your workspace to your local drive.");
buttonExport.setWidth("-1px");
buttonExport.setHeight("-1px");
horizontalLayoutLeftToolbarLeft.addComponent(buttonExport);
return horizontalLayoutLeftToolbarLeft;
}
@AutoGenerated
private VerticalLayout buildVerticalLayoutRightPanel() {
// common part: create layout
verticalLayoutRightPanel = new VerticalLayout();
verticalLayoutRightPanel.setImmediate(false);
verticalLayoutRightPanel.setWidth("100.0%");
verticalLayoutRightPanel.setHeight("-1px");
verticalLayoutRightPanel.setMargin(true);
verticalLayoutRightPanel.setSpacing(true);
// horizontalLayoutRightToolbar
horizontalLayoutRightToolbar = buildHorizontalLayoutRightToolbar();
verticalLayoutRightPanel.addComponent(horizontalLayoutRightToolbar);
// tabSheet
tabSheet = new TabSheet();
tabSheet.setImmediate(true);
tabSheet.setWidth("100.0%");
tabSheet.setHeight("-1px");
verticalLayoutRightPanel.addComponent(tabSheet);
verticalLayoutRightPanel.setExpandRatio(tabSheet, 1.0f);
return verticalLayoutRightPanel;
}
@AutoGenerated
private HorizontalLayout buildHorizontalLayoutRightToolbar() {
// common part: create layout
horizontalLayoutRightToolbar = new HorizontalLayout();
horizontalLayoutRightToolbar.setImmediate(false);
horizontalLayoutRightToolbar.setWidth("100.0%");
horizontalLayoutRightToolbar.setHeight("-1px");
horizontalLayoutRightToolbar.setMargin(false);
horizontalLayoutRightToolbar.setSpacing(true);
// buttonRight
buttonRight = new Button();
buttonRight.setCaption(">>");
buttonRight.setImmediate(true);
buttonRight.setDescription("Restore left panel.");
buttonRight.setWidth("-1px");
buttonRight.setHeight("-1px");
horizontalLayoutRightToolbar.addComponent(buttonRight);
return horizontalLayoutRightToolbar;
}
}