From 12f52bdd224b67106fc50c36ec7ddff73b6151a2 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Sun, 14 Mar 2021 09:58:40 +0300 Subject: [PATCH 01/12] CB-750 Complex value transfer API --- .../io/cloudbeaver/server/CBApplication.java | 2 +- .../service/WebServiceServletBase.java | 46 ++++++++++ .../service/sql/WebSQLResultServlet.java | 66 ++++++++++++++ .../cloudbeaver/service/sql/WebSQLUtils.java | 65 ++++++++++---- .../service/sql/WebServiceBindingSQL.java | 13 ++- .../admin/impl/WebAdminLogsServlet.java | 73 +++++++-------- .../transfer/impl/WebDataTransferServlet.java | 90 ++++++++----------- .../transfer/impl/WebDataTransferUtils.java | 4 +- 8 files changed, 241 insertions(+), 118 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index 8e910da128..2deca20080 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -224,7 +224,7 @@ public class CBApplication extends BaseApplicationImpl { log.debug("\tContent root: " + new File(contentRoot).getAbsolutePath()); log.debug("\tDrivers storage: " + new File(driversLocation).getAbsolutePath()); //log.debug("\tDrivers root: " + driversLocation); - log.debug("\tProduct details: " + application.getInfoDetails()); + //log.debug("\tProduct details: " + application.getInfoDetails()); log.debug("\tBase port: " + serverPort); log.debug("\tBase URI: " + servicesURI); if (develMode) { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java new file mode 100644 index 0000000000..58f68f9f4a --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java @@ -0,0 +1,46 @@ +package io.cloudbeaver.service; + +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.server.CBPlatform; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public abstract class WebServiceServletBase extends HttpServlet { + + private static final Log log = Log.getLog(WebServiceServletBase.class); + + private final CBApplication application; + + public WebServiceServletBase(CBApplication application) { + this.application = application; + } + + public CBApplication getApplication() { + return application; + } + + @Override + protected final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + WebSession webSession = CBPlatform.getInstance().getSessionManager().findWebSession(request); + if (webSession == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Web session not found"); + return; + } + try { + processServiceRequest(webSession, request, response); + } catch (Exception e) { + log.error(e); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + e.getMessage()); + } + } + + protected abstract void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException; + +} \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java new file mode 100644 index 0000000000..7919365c08 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java @@ -0,0 +1,66 @@ +package io.cloudbeaver.service.sql; + +import io.cloudbeaver.DBWebException; +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.service.WebServiceServletBase; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.utils.CommonUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class WebSQLResultServlet extends WebServiceServletBase { + + private static final Log log = Log.getLog(WebSQLResultServlet.class); + + // context-id/result-id/row-number/attribute-name + private static final Pattern URL_PATTERN = Pattern.compile("/?([\\w]+)/([0-9]+)/([0-9]+)/([0-9]+)/(.+)[/\\?]?"); + + private final DBWServiceSQL sqlService; + + public WebSQLResultServlet(CBApplication application, DBWServiceSQL sqlService) { + super(application); + this.sqlService = sqlService; + } + + @Override + protected void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException { + + String valuePath = request.getPathInfo(); + if (CommonUtils.isEmpty(valuePath)) { + throw new DBWebException("Result value ID not specified"); + } + Matcher urlMatcher = URL_PATTERN.matcher(valuePath); + if (!urlMatcher.matches()) { + throw new DBWebException("Invalid URI format"); + } + + String connectionId = urlMatcher.group(1); + String contextId = urlMatcher.group(2); + String resultsId = urlMatcher.group(3); + String rowNum = urlMatcher.group(4); + String attrName = urlMatcher.group(5); + + //sqlService. +/* + response.setHeader("Content-Type", processor.getContentType()); + response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + response.setHeader("Content-Length", String.valueOf(dataFile.length())); + + try (InputStream is = new FileInputStream(dataFile)) { + IOUtils.copyStream(is, response.getOutputStream()); + } + + // TODO: cleanup export files ASAP? + if (false) { + dtConfig.removeTask(taskInfo); + } +*/ + } + +} \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java index 9ed47f73be..e78e2e7d23 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java @@ -18,6 +18,7 @@ package io.cloudbeaver.service.sql; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBConstants; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.data.*; import org.jkiss.dbeaver.model.exec.DBCException; @@ -25,14 +26,12 @@ import org.jkiss.dbeaver.model.gis.DBGeometry; import org.jkiss.dbeaver.model.struct.DBSAttributeBase; import org.jkiss.dbeaver.model.struct.DBSTypedObject; import org.jkiss.dbeaver.utils.ContentUtils; +import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.utils.CommonUtils; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; /** * Web SQL utils. @@ -41,6 +40,8 @@ public class WebSQLUtils { private static final Log log = Log.getLog(WebSQLUtils.class); + public static final int BINARY_PREVIEW_LENGTH = 255; + public static Object makeWebCellValue(WebSession session, DBSTypedObject type, Object cellValue, WebDataFormat dataFormat) throws DBCException { if (cellValue instanceof Date) { return CBConstants.ISO_DATE_FORMAT.format(cellValue); @@ -51,11 +52,7 @@ public class WebSQLUtils { return null; } else if (dbValue instanceof DBDDocument) { - if (dataFormat != WebDataFormat.document) { - return serializeDocumentValue((DBDDocument) dbValue); - } else { - return serializeDocumentValue(session, (DBDDocument) dbValue); - } + return serializeDocumentValue(session, (DBDDocument) dbValue); } else if (dbValue instanceof DBDComplexValue) { return serializeComplexValue(session, (DBDComplexValue)dbValue, dataFormat); } else if (dbValue instanceof DBGeometry) { @@ -75,18 +72,31 @@ public class WebSQLUtils { for (int i = 0; i < size; i++) { items[i] = makeWebCellValue(session, collection.getComponentType(), collection.getItem(i), dataFormat); } - return items; + + Map map = createMapOfType("collection"); + map.put("value", items); + return map; } else if (value instanceof DBDComposite) { DBDComposite composite = (DBDComposite)value; - Map map = new LinkedHashMap<>(); + Map struct = new LinkedHashMap<>(); for (DBSAttributeBase attr : composite.getAttributes()) { - map.put(attr.getName(), makeWebCellValue(session, attr, composite.getAttributeValue(attr), dataFormat)); + struct.put(attr.getName(), makeWebCellValue(session, attr, composite.getAttributeValue(attr), dataFormat)); } + + Map map = createMapOfType("map"); + map.put("value", struct); return map; } return value.toString(); } + @NotNull + private static Map createMapOfType(String type) { + Map map = new LinkedHashMap<>(); + map.put("$type", type); + return map; + } + private static Map serializeDocumentValue(WebSession session, DBDDocument document) throws DBCException { String documentData; try { @@ -97,7 +107,7 @@ public class WebSQLUtils { throw new DBCException("Error serializing document", e); } - Map map = new LinkedHashMap<>(); + Map map = createMapOfType("document"); map.put("id", CommonUtils.toString(document.getDocumentId())); map.put("contentType", document.getDocumentContentType()); map.put("properties", Collections.emptyMap()); @@ -106,15 +116,34 @@ public class WebSQLUtils { } private static Object serializeContentValue(WebSession session, DBDContent value) throws DBCException { - return ContentUtils.getContentStringValue(session.getProgressMonitor(), value); - } - private static Object serializeDocumentValue(DBDDocument value) { - return value; + Map map = createMapOfType("content"); + if (ContentUtils.isTextContent(value)) { + String stringValue = ContentUtils.getContentStringValue(session.getProgressMonitor(), value); + map.put("text", stringValue); + } else { + map.put("binary", true); + byte[] binaryValue = ContentUtils.getContentBinaryValue(session.getProgressMonitor(), value); + if (binaryValue != null) { + if (binaryValue.length > BINARY_PREVIEW_LENGTH) { + binaryValue = Arrays.copyOf(binaryValue, BINARY_PREVIEW_LENGTH); + } + map.put("text", GeneralUtils.convertToString(binaryValue, 0, binaryValue.length)); + } else { + map.put("text", null); + } + } + map.put("contentType", value.getContentType()); + map.put("contentLength", value.getContentLength()); + return map; } private static Object serializeGeometryValue(DBGeometry value) { - return value.toString(); + Map map = createMapOfType("geometry"); + map.put("srid", value.getSRID()); + map.put("text", value.toString()); + map.put("properties", value.getProperties()); + return map; } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java index 87d4b7d2d6..6246ecf37a 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java @@ -20,9 +20,13 @@ import graphql.schema.DataFetchingEnvironment; import io.cloudbeaver.DBWebException; import io.cloudbeaver.model.WebConnectionInfo; import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.DBWBindingContext; +import io.cloudbeaver.service.DBWServiceBindingServlet; import io.cloudbeaver.service.WebServiceBindingBase; import io.cloudbeaver.service.sql.impl.WebServiceSQL; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; import org.jkiss.dbeaver.DBException; import org.jkiss.utils.CommonUtils; @@ -34,7 +38,7 @@ import java.util.stream.Collectors; /** * Web service implementation */ -public class WebServiceBindingSQL extends WebServiceBindingBase { +public class WebServiceBindingSQL extends WebServiceBindingBase implements DBWServiceBindingServlet { public WebServiceBindingSQL() { super(DBWServiceSQL.class, new WebServiceSQL(), "schema/service.sql.graphqls"); @@ -133,6 +137,13 @@ public class WebServiceBindingSQL extends WebServiceBindingBase { return context; } + @Override + public void addServlets(CBApplication application, ServletContextHandler servletContextHandler) { + servletContextHandler.addServlet( + new ServletHolder("sqlResultValueViewer", new WebSQLResultServlet(application, getServiceImpl())), + application.getServicesURI() + "sql-result-value/*"); + } + private static class WebSQLConfiguration { private final Map processors = new HashMap<>(); diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebAdminLogsServlet.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebAdminLogsServlet.java index e2bc27b89c..afc10e854d 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebAdminLogsServlet.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebAdminLogsServlet.java @@ -4,15 +4,14 @@ import io.cloudbeaver.DBWConstants; import io.cloudbeaver.DBWebException; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBApplication; -import io.cloudbeaver.server.CBPlatform; +import io.cloudbeaver.service.WebServiceServletBase; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBConstants; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.dbeaver.utils.MimeTypes; import org.jkiss.utils.IOUtils; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; @@ -20,55 +19,43 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -public class WebAdminLogsServlet extends HttpServlet { +public class WebAdminLogsServlet extends WebServiceServletBase { private static final Log log = Log.getLog(WebAdminLogsServlet.class); - private final CBApplication application; - public WebAdminLogsServlet(CBApplication application) { - this.application = application; + super(application); } @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - try { - WebSession webSession = CBPlatform.getInstance().getSessionManager().findWebSession(request); - if (webSession == null) { - throw new DBWebException("No active session"); - } - if (!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "Logs accessible for admins only"); - return; - } - - String logId = request.getPathInfo(); - String logFileName = ".log"; - if ("debug".equals(logId)) { - logFileName = DBConstants.DEBUG_LOG_FILE_NAME; - } - if (logFileName.contains("/") || logFileName.contains("\\")) { - throw new DBWebException("Bad log file name"); - } - File logFile = new File(GeneralUtils.getMetadataFolder(), logFileName); - if (!logFile.exists()) { - throw new DBWebException("Log file '" + logFileName + "' not found"); - } - - response.setHeader("Content-Type", MimeTypes.TEXT_PLAIN); - if (logFileName.equals(".log")) { - logFileName = "server.log"; - } - response.setHeader("Content-Disposition", "attachment; filename=\"" + logFileName + "\""); - - try (InputStream is = new FileInputStream(logFile)) { - IOUtils.copyStream(is, response.getOutputStream()); - } - } catch (Exception e) { - log.error(e); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error reading log: " + e.getMessage()); + protected void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException { + if (!session.hasPermission(DBWConstants.PERMISSION_ADMIN)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Logs accessible for admins only"); return; } + + String logId = request.getPathInfo(); + String logFileName = ".log"; + if ("debug".equals(logId)) { + logFileName = DBConstants.DEBUG_LOG_FILE_NAME; + } + if (logFileName.contains("/") || logFileName.contains("\\")) { + throw new DBWebException("Bad log file name"); + } + File logFile = new File(GeneralUtils.getMetadataFolder(), logFileName); + if (!logFile.exists()) { + throw new DBWebException("Log file '" + logFileName + "' not found"); + } + + response.setHeader("Content-Type", MimeTypes.TEXT_PLAIN); + if (logFileName.equals(".log")) { + logFileName = "server.log"; + } + response.setHeader("Content-Disposition", "attachment; filename=\"" + logFileName + "\""); + + try (InputStream is = new FileInputStream(logFile)) { + IOUtils.copyStream(is, response.getOutputStream()); + } } } \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferServlet.java b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferServlet.java index ba9d730409..f787740570 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferServlet.java +++ b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferServlet.java @@ -1,18 +1,17 @@ package io.cloudbeaver.service.data.transfer.impl; import io.cloudbeaver.DBWebException; -import io.cloudbeaver.server.CBPlatform; -import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.service.WebServiceServletBase; import io.cloudbeaver.service.data.transfer.DBWServiceDataTransfer; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.tools.transfer.registry.DataTransferProcessorDescriptor; import org.jkiss.dbeaver.tools.transfer.registry.DataTransferRegistry; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.IOUtils; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; @@ -20,69 +19,56 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -public class WebDataTransferServlet extends HttpServlet { +public class WebDataTransferServlet extends WebServiceServletBase { private static final Log log = Log.getLog(WebDataTransferServlet.class); - private final CBApplication application; private final DBWServiceDataTransfer dtManager; public WebDataTransferServlet(CBApplication application, DBWServiceDataTransfer dtManager) { - this.application = application; + super(application); this.dtManager = dtManager; } @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - try { - String dataFileId = request.getPathInfo(); - if (CommonUtils.isEmpty(dataFileId)) { - throw new DBWebException("Data ID not specified"); - } - while (dataFileId.startsWith("/")) { - dataFileId = dataFileId.substring(1); - } + protected void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException { + String dataFileId = request.getPathInfo(); + if (CommonUtils.isEmpty(dataFileId)) { + throw new DBWebException("Data ID not specified"); + } + while (dataFileId.startsWith("/")) { + dataFileId = dataFileId.substring(1); + } - WebSession webSession = CBPlatform.getInstance().getSessionManager().findWebSession(request); - if (webSession == null) { - throw new DBWebException("No active session"); - } - WebDataTransferSessionConfig dtConfig = WebDataTransferUtils.getSessionDataTransferConfig(webSession); - WebDataTransferTaskConfig taskInfo = dtConfig.getTask(dataFileId); - if (taskInfo == null) { - throw new DBWebException("Session task '" + dataFileId + "' not found"); - } - DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(taskInfo.getParameters().getProcessorId()); - if (processor == null) { - throw new DBWebException("Wrong data processor '" + taskInfo.getParameters().getProcessorId() + "'"); - } - String fileName = taskInfo.getExportFileName(); - if (!CommonUtils.isEmpty(fileName)) { - fileName += "." + WebDataTransferUtils.getProcessorFileExtension(processor); - } else { - fileName = taskInfo.getDataFileId(); - } + WebDataTransferSessionConfig dtConfig = WebDataTransferUtils.getSessionDataTransferConfig(session); + WebDataTransferTaskConfig taskInfo = dtConfig.getTask(dataFileId); + if (taskInfo == null) { + throw new DBWebException("Session task '" + dataFileId + "' not found"); + } + DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(taskInfo.getParameters().getProcessorId()); + if (processor == null) { + throw new DBWebException("Wrong data processor '" + taskInfo.getParameters().getProcessorId() + "'"); + } + String fileName = taskInfo.getExportFileName(); + if (!CommonUtils.isEmpty(fileName)) { + fileName += "." + WebDataTransferUtils.getProcessorFileExtension(processor); + } else { + fileName = taskInfo.getDataFileId(); + } - File dataFile = taskInfo.getDataFile(); + File dataFile = taskInfo.getDataFile(); - response.setHeader("Content-Type", processor.getContentType()); - response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); - response.setHeader("Content-Length", String.valueOf(dataFile.length())); + response.setHeader("Content-Type", processor.getContentType()); + response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + response.setHeader("Content-Length", String.valueOf(dataFile.length())); - try (InputStream is = new FileInputStream(dataFile)) { - IOUtils.copyStream(is, response.getOutputStream()); - } + try (InputStream is = new FileInputStream(dataFile)) { + IOUtils.copyStream(is, response.getOutputStream()); + } - // TODO: cleanup export files ASAP? - if (false) { - dtConfig.removeTask(taskInfo); - } - - //response.ok(HttpServletResponse.SC_BAD_REQUEST, "Not implemented"); - } catch (Exception e) { - log.error(e); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error reading data: " + e.getMessage()); - return; + // TODO: cleanup export files ASAP? + if (false) { + dtConfig.removeTask(taskInfo); } } diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferUtils.java b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferUtils.java index 79c88ef71f..40641112cf 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferUtils.java +++ b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferUtils.java @@ -6,9 +6,7 @@ import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.tools.transfer.registry.DataTransferProcessorDescriptor; import org.jkiss.utils.CommonUtils; -import javax.servlet.http.HttpServlet; - -public class WebDataTransferUtils extends HttpServlet { +class WebDataTransferUtils { private static final Log log = Log.getLog(WebDataTransferUtils.class); From 87457951eea5da48ada14a38827e157bd83e98c1 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Sun, 14 Mar 2021 10:21:02 +0300 Subject: [PATCH 02/12] SQL value read servlet draft --- .../service/WebServiceBindingBase.java | 9 ++++++++- .../service/sql/WebSQLResultServlet.java | 9 ++++++++- .../service/sql/WebServiceBindingSQL.java | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index e666cce5a9..547a746ab1 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -25,6 +25,7 @@ import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.server.graphql.GraphQLEndpoint; +import org.jkiss.code.NotNull; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; @@ -107,8 +108,14 @@ public abstract class WebServiceBindingBase impleme getServletRequest(env)); } + @NotNull protected static WebConnectionInfo getWebConnection(DataFetchingEnvironment env) throws DBWebException { - return getWebSession(env).getWebConnectionInfo(env.getArgument("connectionId")); + return getWebConnection(getWebSession(env), env.getArgument("connectionId")); + } + + @NotNull + public static WebConnectionInfo getWebConnection(WebSession session, String connectionId) throws DBWebException { + return session.getWebConnectionInfo(connectionId); } private class ServiceInvocationHandler implements InvocationHandler { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java index 7919365c08..929f38f7bd 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java @@ -1,6 +1,7 @@ package io.cloudbeaver.service.sql; import io.cloudbeaver.DBWebException; +import io.cloudbeaver.model.WebConnectionInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.WebServiceServletBase; @@ -46,7 +47,13 @@ public class WebSQLResultServlet extends WebServiceServletBase { String rowNum = urlMatcher.group(4); String attrName = urlMatcher.group(5); - //sqlService. + WebConnectionInfo webConnection = WebServiceBindingSQL.getWebConnection(session, connectionId); + WebSQLProcessor sqlProcessor = WebServiceBindingSQL.getSQLProcessor(webConnection); + WebSQLContextInfo sqlContext = WebServiceBindingSQL.getSQLContext(sqlProcessor, contextId); + WebSQLResultsInfo sqlResults = sqlContext.getResults(resultsId); + + + throw new DBWebException("Not implemented yet"); /* response.setHeader("Content-Type", processor.getContentType()); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java index 6246ecf37a..652205c81b 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java @@ -27,6 +27,7 @@ import io.cloudbeaver.service.WebServiceBindingBase; import io.cloudbeaver.service.sql.impl.WebServiceSQL; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.utils.CommonUtils; @@ -113,23 +114,37 @@ public class WebServiceBindingSQL extends WebServiceBindingBase i )); } + @NotNull private WebDataFormat getDataFormat(DataFetchingEnvironment env) { String dataFormat = env.getArgument("dataFormat"); return CommonUtils.valueOf(WebDataFormat.class, dataFormat, WebDataFormat.resultset); } + @NotNull public static WebSQLConfiguration getSQLConfiguration(WebSession webSession) { return webSession.getAttribute("sqlConfiguration", cfg -> new WebSQLConfiguration(), WebSQLConfiguration::dispose); } + @NotNull public static WebSQLProcessor getSQLProcessor(DataFetchingEnvironment env) throws DBWebException { WebConnectionInfo connectionInfo = getWebConnection(env); + return getSQLProcessor(connectionInfo); + } + + @NotNull + public static WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo) throws DBWebException { return getSQLConfiguration(connectionInfo.getSession()).getSQLProcessor(connectionInfo); } + @NotNull public static WebSQLContextInfo getSQLContext(DataFetchingEnvironment env) throws DBWebException { WebSQLProcessor processor = getSQLProcessor(env); String contextId = env.getArgument("contextId"); + return getSQLContext(processor, contextId); + } + + @NotNull + public static WebSQLContextInfo getSQLContext(WebSQLProcessor processor, String contextId) throws DBWebException { WebSQLContextInfo context = processor.getContext(contextId); if (context == null) { throw new DBWebException("SQL context '" + contextId + "' not found"); From a9abe7d535382b208d955eeb410a7956b120cd01 Mon Sep 17 00:00:00 2001 From: Wroud Date: Sun, 14 Mar 2021 15:32:09 +0300 Subject: [PATCH 03/12] chore: update deps --- webapp/package.json | 2 +- webapp/packages/core-blocks/package.json | 2 +- webapp/yarn.lock | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index f1d2558599..c0379fba06 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -45,7 +45,7 @@ "reakit": "^1.3.4", "reflect-metadata": "^0.1.13", "reshadow": "^0.0.1-alpha.78", - "typescript": "^4.1.3" + "typescript": "^4.2.3" }, "dependencies": {} } \ No newline at end of file diff --git a/webapp/packages/core-blocks/package.json b/webapp/packages/core-blocks/package.json index 7ab2888983..d09851ae2e 100644 --- a/webapp/packages/core-blocks/package.json +++ b/webapp/packages/core-blocks/package.json @@ -15,7 +15,7 @@ "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" }, "dependencies": { - "go-split": "^1.x.x", + "go-split": "^2.x.x", "sanitize-html": "^1.x.x", "react-sanitized-html": "^2.x.x" }, diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 5dd1f88812..1f1e6561d5 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -6842,10 +6842,10 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -go-split@^1.x.x: - version "1.3.2" - resolved "https://registry.yarnpkg.com/go-split/-/go-split-1.3.2.tgz#670fcd9316c6fe324d32429171e70ac31d344aae" - integrity sha512-f/7Qz2HDjRBAOOqJi5JJzSl83k47Zz0xAmzFo6rETRDbGvp6bqJERq/YgjvoUVXUw8J4OiOuTnEHEQrwcH5f4Q== +go-split@^2.x.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/go-split/-/go-split-2.0.0.tgz#07c1ce0fd9072d20c041a5c70b07f2b267916973" + integrity sha512-uPCOAIUl9L3i+3CQPBdu7fdx3vYtnQnPPOV//rjkqRaIEXK2kZKKFj+ZYMAbL7u0KFRJ+RAOrkPzv1qUU6yFUA== got@^9.6.0: version "9.6.0" @@ -13079,10 +13079,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== +typescript@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" + integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== ua-parser-js@^0.7.18: version "0.7.23" From 8c0a143377c033f4a8c16efa46712c367c8bed2e Mon Sep 17 00:00:00 2001 From: serge-rider Date: Sun, 14 Mar 2021 15:59:33 +0300 Subject: [PATCH 04/12] CB-793 Auth improvements: multi-auth allowed, user checks added. AWS user info read fix --- .../src/io/cloudbeaver/DBWAuthProviderExternal.java | 7 +++++-- .../io/cloudbeaver/model/user/WebUserOriginInfo.java | 2 +- .../cloudbeaver/service/sql/WebSQLResultServlet.java | 5 ++--- .../service/auth/impl/WebServiceAuthImpl.java | 11 +++++++++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java index a343e7a2af..f781b2e3b0 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java @@ -16,6 +16,7 @@ */ package io.cloudbeaver; +import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebUser; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; @@ -49,7 +50,8 @@ public interface DBWAuthProviderExternal extend @NotNull DBRProgressMonitor monitor, @NotNull DBWSecurityController securityController, @NotNull Map providerConfig, - @NotNull Map credentials) throws DBException; + @NotNull Map credentials, + @Nullable WebUser activeUser) throws DBException; @Nullable String getUserDisplayName( @@ -60,7 +62,8 @@ public interface DBWAuthProviderExternal extend @Nullable DBPObject getUserDetails( @NotNull DBRProgressMonitor monitor, - AUTH_SESSION session, + @NotNull WebSession webSession, + @NotNull AUTH_SESSION session, @NotNull WebUser user) throws DBException; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java index caf9aeeade..d57ad29a3c 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java @@ -97,7 +97,7 @@ public class WebUserOriginInfo implements WebObjectOrigin { if (!isValidSessionType(authSession, authProvider)) { return new WebPropertyInfo[0]; } - DBPObject userDetails = ((DBWAuthProviderExternal) authProvider).getUserDetails(session.getProgressMonitor(), authSession, user); + DBPObject userDetails = ((DBWAuthProviderExternal) authProvider).getUserDetails(session.getProgressMonitor(), session, authSession, user); if (userDetails != null) { return WebServiceUtils.getObjectProperties(session, userDetails); } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java index 929f38f7bd..8c8bbbddb9 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java @@ -1,7 +1,6 @@ package io.cloudbeaver.service.sql; import io.cloudbeaver.DBWebException; -import io.cloudbeaver.model.WebConnectionInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.WebServiceServletBase; @@ -47,8 +46,8 @@ public class WebSQLResultServlet extends WebServiceServletBase { String rowNum = urlMatcher.group(4); String attrName = urlMatcher.group(5); - WebConnectionInfo webConnection = WebServiceBindingSQL.getWebConnection(session, connectionId); - WebSQLProcessor sqlProcessor = WebServiceBindingSQL.getSQLProcessor(webConnection); + WebSQLProcessor sqlProcessor = WebServiceBindingSQL.getSQLProcessor( + WebServiceBindingSQL.getWebConnection(session, connectionId)); WebSQLContextInfo sqlContext = WebServiceBindingSQL.getSQLContext(sqlProcessor, contextId); WebSQLResultsInfo sqlResults = sqlContext.getResults(resultsId); diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index 4d0b51d82b..a92d30359d 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -27,6 +27,7 @@ import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.service.auth.DBWServiceAuth; import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.access.DBASession; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.utils.CommonUtils; @@ -40,6 +41,8 @@ import java.util.Map; */ public class WebServiceAuthImpl implements DBWServiceAuth { + private static final Log log = Log.getLog(WebServiceAuthImpl.class); + @Override public WebAuthInfo authLogin(WebSession webSession, String providerId, Map authParameters) throws DBWebException { DBWSecurityController serverController = CBPlatform.getInstance().getApplication().getSecurityController(); @@ -61,12 +64,12 @@ public class WebServiceAuthImpl implements DBWServiceAuth { authParameters = authProviderExternal.readExternalCredentials(webSession.getProgressMonitor(), providerConfig, authParameters); } - WebUser user = null; + WebUser user = webSession.getUser(); String userId = serverController.getUserByCredentials(authProvider, authParameters); if (userId == null) { // User doesn't exist. We can create new user automatically if auth provider supports this if (authProviderExternal != null) { - user = authProviderExternal.registerNewUser(webSession.getProgressMonitor(), serverController, providerConfig, authParameters); + user = authProviderExternal.registerNewUser(webSession.getProgressMonitor(), serverController, providerConfig, authParameters, user); userId = user.getUserId(); } @@ -74,6 +77,10 @@ public class WebServiceAuthImpl implements DBWServiceAuth { throw new DBCException("Invalid user credentials"); } } + if (user != null && !user.getUserId().equals(userId)) { + log.debug("Attempt to authorize user '" + userId + "' while user '" + user.getUserId() + "' already authorized"); + throw new DBCException("You cannot authorize with different users credentials"); + } // Check for auth enabled. Auth is always enabled for admins if (!CBApplication.getInstance().getAppConfiguration().isAuthenticationEnabled()) { From c15db2abaa071358d93d92611ea9f904f6ea5863 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Sun, 14 Mar 2021 16:55:44 +0300 Subject: [PATCH 05/12] CB-793 Multi-auth model --- .../cloudbeaver/model/session/WebSession.java | 99 ++++++++++++++----- .../model/user/WebUserOriginInfo.java | 9 +- .../schema/service.auth.graphqls | 5 +- .../service/auth/DBWServiceAuth.java | 10 +- .../service/auth/WebServiceBindingAuth.java | 2 +- .../service/auth/impl/WebServiceAuthImpl.java | 16 +-- 6 files changed, 99 insertions(+), 42 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java index b61f7267f2..3c36625a08 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java @@ -97,7 +97,8 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap private final Map asyncTasks = new HashMap<>(); private final Map attributes = new HashMap<>(); private final Map> attributeDisposers = new HashMap<>(); - private WebAuthInfo authInfo; + // Map of auth tokens. Key is authentication provdier + private final List authTokens = new ArrayList<>(); private DBNModel navigatorModel; private DBRProgressMonitor progressMonitor = new SessionProgressMonitor(); @@ -477,10 +478,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap } catch (Throwable e) { log.error(e); } - if (this.authInfo != null) { - this.authInfo.closeAuth(); - this.authInfo = null; - } + clearAuthTokens(); this.user = null; if (this.sessionProject != null) { @@ -489,6 +487,16 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap } } + private void clearAuthTokens() { + ArrayList tokensCopy; + synchronized (authTokens) { + tokensCopy = new ArrayList<>(this.authTokens); + } + for (WebAuthInfo ai : tokensCopy) { + removeAuthInfo(ai); + } + } + public DBRProgressMonitor getProgressMonitor() { return progressMonitor; } @@ -622,30 +630,63 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap } } - public WebAuthInfo getAuthInfo() { - return authInfo; + public WebAuthInfo getAuthInfo(@Nullable String providerID) { + synchronized (authTokens) { + + if (providerID != null) { + for (WebAuthInfo ai : authTokens) { + if (ai.getAuthProvider().getId().equals(providerID)) { + return ai; + } + } + return null; + } + return authTokens.isEmpty() ? null : authTokens.get(0); + } } - public void setAuthInfo(@Nullable WebAuthInfo authInfo) { - WebUser newUser = authInfo == null ? null : authInfo.getUser(); - if (CommonUtils.equalObjects(this.user, newUser)) { - return; + public void addAuthInfo(@NotNull WebAuthInfo authInfo) throws DBException { + WebUser newUser = authInfo.getUser(); + if (this.user == null && newUser != null) { + forceUserRefresh(newUser); + } else if (!CommonUtils.equalObjects(this.user, newUser)) { + throw new DBException("Can't authorize different users in the single session"); } - forceUserRefresh(newUser); - if (this.authInfo != null) { - DBASession oldAuthSession = this.authInfo.getAuthSession(); - if (oldAuthSession != null) { - sessionProject.getSessionContext().removeSession(oldAuthSession); - } - this.authInfo.closeAuth(); + + WebAuthInfo oldAuthInfo = getAuthInfo(authInfo.getAuthProvider().getId()); + if (oldAuthInfo != null) { + removeAuthInfo(oldAuthInfo); } - this.authInfo = authInfo; - if (authInfo != null) { - DBASession authSession = authInfo.getAuthSession(); - if (authSession != null) { - this.sessionProject.getSessionContext().addSession(authSession); - } + + synchronized (authTokens) { + authTokens.add(authInfo); + } + DBASession authSession = authInfo.getAuthSession(); + if (authSession != null) { + this.sessionProject.getSessionContext().addSession(authSession); + } + } + + private void removeAuthInfo(WebAuthInfo oldAuthInfo) { + DBASession oldAuthSession = oldAuthInfo.getAuthSession(); + if (oldAuthSession != null && sessionProject != null) { + sessionProject.getSessionContext().removeSession(oldAuthSession); + } + oldAuthInfo.closeAuth(); + synchronized (authTokens) { + authTokens.remove(oldAuthInfo); + } + } + + public void removeAuthInfo(String providerId) { + if (providerId == null) { + clearAuthTokens(); + } else { + removeAuthInfo(getAuthInfo(providerId)); + } + if (authTokens.isEmpty()) { + forceUserRefresh(null); } } @@ -705,9 +746,13 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap // May be called to extract auth information from session @Override public T getAdapter(Class adapter) { - if (authInfo != null && authInfo.getAuthSession() != null) { - if (adapter.isInstance(authInfo.getAuthSession())) { - return adapter.cast(authInfo.getAuthSession()); + synchronized (authTokens) { + for (WebAuthInfo authInfo : authTokens) { + if (authInfo != null && authInfo.getAuthSession() != null) { + if (adapter.isInstance(authInfo.getAuthSession())) { + return adapter.cast(authInfo.getAuthSession()); + } + } } } return null; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java index d57ad29a3c..b291dcab21 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebUserOriginInfo.java @@ -23,10 +23,12 @@ import io.cloudbeaver.WebServiceUtils; import io.cloudbeaver.auth.provider.local.LocalAuthProvider; import io.cloudbeaver.model.WebObjectOrigin; import io.cloudbeaver.model.WebPropertyInfo; +import io.cloudbeaver.model.session.WebAuthInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.registry.WebAuthProviderDescriptor; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPObject; import org.jkiss.dbeaver.model.access.DBASession; @@ -91,7 +93,12 @@ public class WebUserOriginInfo implements WebObjectOrigin { return new WebPropertyInfo[0]; } try { - DBASession authSession = session.getAuthInfo().getAuthSession(); + WebAuthInfo authInfo = session.getAuthInfo( + LocalAuthProvider.PROVIDER_ID.equals(authProvider.getId()) ? null : authProvider.getId()); + if (authInfo == null) { + throw new DBException("Session not authorized in auth provider '" + authProvider.getId() + "'"); + } + DBASession authSession = authInfo.getAuthSession(); DBWAuthProvider authProvider = this.authProvider.getInstance(); if (authSession != null && authProvider instanceof DBWAuthProviderExternal) { if (!isValidSessionType(authSession, authProvider)) { diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index e40e3f0a50..736015c54a 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -48,9 +48,10 @@ type UserAuthInfo { extend type Query { + # Authorize user using specified auth provider authLogin(provider: ID!, credentials: Object!): UserAuthInfo! - - authLogout: Boolean + # Logouts user. If provider not specified then all authorizations are revoked from session. + authLogout(provider: ID): Boolean sessionUser: UserAuthInfo diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java index 52d42e04a0..facbfe94ba 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java @@ -22,6 +22,8 @@ import io.cloudbeaver.WebAction; import io.cloudbeaver.model.session.WebAuthInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebAuthProviderInfo; +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; import java.util.Map; @@ -31,14 +33,14 @@ import java.util.Map; public interface DBWServiceAuth extends DBWService { @WebAction(requirePermissions = {} ) - WebAuthInfo authLogin(WebSession webSession, String providerId, Map credentials) throws DBWebException; + WebAuthInfo authLogin(@NotNull WebSession webSession, @NotNull String providerId, @NotNull Map credentials) throws DBWebException; @WebAction(requirePermissions = {} ) - void authLogout(WebSession webSession) throws DBWebException; + void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException; @WebAction(requirePermissions = {}) - WebAuthInfo sessionUser(WebSession webSession) throws DBWebException; + WebAuthInfo sessionUser(@NotNull WebSession webSession) throws DBWebException; @WebAction(requirePermissions = {}) - WebAuthProviderInfo[] getAuthProviders(WebSession webSession); + WebAuthProviderInfo[] getAuthProviders(@NotNull WebSession webSession); } diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java index 7e412882b7..5e1434ac68 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java @@ -40,7 +40,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase env.getArgument("provider"), env.getArgument("credentials"))) .dataFetcher("authLogout", env -> { - getService(env).authLogout(getWebSession(env)); + getService(env).authLogout(getWebSession(env), env.getArgument("provider")); return true; }) .dataFetcher("sessionUser", env -> getService(env).sessionUser(getWebSession(env, false))) diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index a92d30359d..58df254883 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -26,6 +26,8 @@ import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.service.auth.DBWServiceAuth; +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.access.DBASession; @@ -44,7 +46,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { private static final Log log = Log.getLog(WebServiceAuthImpl.class); @Override - public WebAuthInfo authLogin(WebSession webSession, String providerId, Map authParameters) throws DBWebException { + public WebAuthInfo authLogin(@NotNull WebSession webSession, @NotNull String providerId, @NotNull Map authParameters) throws DBWebException { DBWSecurityController serverController = CBPlatform.getInstance().getApplication().getSecurityController(); if (CommonUtils.isEmpty(providerId)) { @@ -109,7 +111,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth { authInfo.setAuthProvider(authProvider); authInfo.setAuthSession(authSession); authInfo.setMessage("Authenticated with " + authProvider.getLabel() + " provider"); - webSession.setAuthInfo(authInfo); + webSession.addAuthInfo(authInfo); return authInfo; } catch (DBException e) { @@ -118,23 +120,23 @@ public class WebServiceAuthImpl implements DBWServiceAuth { } @Override - public void authLogout(WebSession webSession) throws DBWebException { + public void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException { if (webSession.getUser() == null) { throw new DBWebException("Not logged in"); } - webSession.setAuthInfo(null); + webSession.removeAuthInfo(providerId); } @Override - public WebAuthInfo sessionUser(WebSession webSession) throws DBWebException { + public WebAuthInfo sessionUser(@NotNull WebSession webSession) throws DBWebException { if (webSession.getUser() == null) { return null; } - return webSession.getAuthInfo(); + return webSession.getAuthInfo(null); } @Override - public WebAuthProviderInfo[] getAuthProviders(WebSession webSession) { + public WebAuthProviderInfo[] getAuthProviders(@NotNull WebSession webSession) { return WebServiceRegistry.getInstance().getAuthProviders() .stream().map(WebAuthProviderInfo::new) .toArray(WebAuthProviderInfo[]::new); From 6ababe41c6d80e43674aedfe320889e36eea5ea2 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Sun, 14 Mar 2021 17:16:01 +0300 Subject: [PATCH 06/12] CB-793 Auth API additions --- .../cloudbeaver/model/session/WebSession.java | 6 +++ .../schema/service.auth.graphqls | 42 ++++++++++++--- .../service/auth/DBWServiceAuth.java | 4 ++ .../service/auth/WebServiceBindingAuth.java | 1 + .../cloudbeaver/service/auth/WebUserInfo.java | 53 +++++++++++++++++++ .../service/auth/impl/WebServiceAuthImpl.java | 9 ++++ 6 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java index 3c36625a08..56f74fab8e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java @@ -645,6 +645,12 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap } } + public List getAllAuthInfo() { + synchronized (authTokens) { + return new ArrayList<>(authTokens); + } + } + public void addAuthInfo(@NotNull WebAuthInfo authInfo) throws DBException { WebUser newUser = authInfo.getUser(); if (this.user == null && newUser != null) { diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index 736015c54a..817468382b 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -30,30 +30,56 @@ type AuthProviderInfo { credentialParameters: [AuthCredentialInfo!]! } -type UserAuthInfo { - # User unique identifier - userId: String! - # Human readable display name. May be null - displayName: String - # Auth provider ID - authProvider: String! +type UserAuthToken { + # Auth provider used for autgorization + provider: ID! + # Authorization time loginTime: DateTime! # Optional login message message: String + # Auth origin origin: ObjectOrigin! } +type UserAuthInfo { + # User unique identifier + userId: String! @deprecated + # Human readable display name. May be null + displayName: String @deprecated + # Auth provider ID + authProvider: String! @deprecated + + loginTime: DateTime! @deprecated + + # Optional login message + message: String @deprecated + + origin: ObjectOrigin! @deprecated +} + +type UserInfo { + # User unique identifier + userId: String! + # Human readable display name. May be null + displayName: String + + authTokens: [UserAuthToken!]! +} + extend type Query { # Authorize user using specified auth provider + authLogin(provider: ID!, credentials: Object!): UserAuthInfo! # Logouts user. If provider not specified then all authorizations are revoked from session. authLogout(provider: ID): Boolean - sessionUser: UserAuthInfo + activeUser: UserInfo + + sessionUser: UserAuthInfo @deprecated authProviders: [AuthProviderInfo!]! diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java index facbfe94ba..e6cea14af4 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java @@ -38,6 +38,10 @@ public interface DBWServiceAuth extends DBWService { @WebAction(requirePermissions = {} ) void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException; + @WebAction(requirePermissions = {}) + WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException; + + @Deprecated @WebAction(requirePermissions = {}) WebAuthInfo sessionUser(@NotNull WebSession webSession) throws DBWebException; diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java index 5e1434ac68..87a3ba7e9b 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java @@ -43,6 +43,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase getService(env).authLogout(getWebSession(env), env.getArgument("provider")); return true; }) + .dataFetcher("activeUser", env -> getService(env).activeUser(getWebSession(env, false))) .dataFetcher("sessionUser", env -> getService(env).sessionUser(getWebSession(env, false))) .dataFetcher("authProviders", env -> getService(env).getAuthProviders(getWebSession(env))) ; diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java new file mode 100644 index 0000000000..53aec5fc71 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java @@ -0,0 +1,53 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2021 DBeaver Corp and others + * + * Licensed 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 io.cloudbeaver.service.auth; + +import io.cloudbeaver.model.session.WebAuthInfo; +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.model.user.WebUser; +import org.jkiss.dbeaver.Log; + +import java.util.List; + +/** + * WebUserInfo + */ +public class WebUserInfo { + + private static final Log log = Log.getLog(WebUserInfo.class); + + private final WebSession session; + private final WebUser user; + + public WebUserInfo(WebSession session, WebUser user) { + this.session = session; + this.user = user; + } + + public String getUserId() { + return user.getUserId(); + } + + public String getDisplayName() { + return user.getDisplayName(); + } + + public List getAuthTokens() { + return session.getAllAuthInfo(); + } + +} diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index 58df254883..18e315e983 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -26,6 +26,7 @@ import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.service.auth.DBWServiceAuth; +import io.cloudbeaver.service.auth.WebUserInfo; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; @@ -127,6 +128,14 @@ public class WebServiceAuthImpl implements DBWServiceAuth { webSession.removeAuthInfo(providerId); } + @Override + public WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException { + if (webSession.getUser() == null) { + return null; + } + return new WebUserInfo(webSession, webSession.getUser()); + } + @Override public WebAuthInfo sessionUser(@NotNull WebSession webSession) throws DBWebException { if (webSession.getUser() == null) { From 99e478010ca27d524116cf942c542447aad25fbd Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 15 Mar 2021 20:01:56 +0300 Subject: [PATCH 07/12] chore: update go-split --- webapp/yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 1f1e6561d5..ba987e8a55 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -6842,10 +6842,10 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -go-split@^2.x.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/go-split/-/go-split-2.0.0.tgz#07c1ce0fd9072d20c041a5c70b07f2b267916973" - integrity sha512-uPCOAIUl9L3i+3CQPBdu7fdx3vYtnQnPPOV//rjkqRaIEXK2kZKKFj+ZYMAbL7u0KFRJ+RAOrkPzv1qUU6yFUA== +go-split@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/go-split/-/go-split-2.0.1.tgz#a7cae7d738c6b8b72fae98056aca64dbf6ad05fc" + integrity sha512-BeGVZ35Aez2frAE8yGUyr0R88r6im23N97ex1WzPWYpQoOJAmhcHalVuvlBTL8yfH6TUC/HFhB81fcuXjSeQbg== got@^9.6.0: version "9.6.0" From 2a90b62338b8cdf2c35f4457683e2e8e6162df48 Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 15 Mar 2021 20:02:13 +0300 Subject: [PATCH 08/12] feat: CB-751 value panel --- .../src/FormControls/TextareaNew.tsx | 27 +++- .../core-blocks/src/Tabs/Tab/TabDefault.tsx | 4 +- .../core-blocks/src/Tabs/Tab/TabProps.ts | 4 +- .../src/Tabs/Tab/verticalRotatedTabStyles.ts | 1 + .../packages/core-blocks/src/Tabs/TabList.tsx | 4 +- .../core-blocks/src/Tabs/TabPanelList.tsx | 4 +- .../core-blocks/src/Tabs/TabsState.tsx | 15 ++- webapp/packages/core-theming/src/useStyles.ts | 5 +- .../DataGrid/CellRenderer/CellRenderer.tsx | 2 +- .../src/DataGrid/Formatters/CellFormatter.tsx | 31 ++--- .../src/styles/themes/_base-react-grid.scss | 18 ++- .../public/icons/text_value_presentation.png | Bin 0 -> 757 bytes .../TextValuePresentation.tsx | 64 ++++++++++ .../TextValuePresentationBootstrap.ts | 33 +++++ .../src/DataPresentationService.ts | 65 +++++++--- .../src/DataViewerBootstrap.ts | 12 +- .../src/DataViewerPage/DataViewerPanel.tsx | 17 +++ .../src/DataViewerTabService.ts | 14 ++- .../Actions/IDatabaseDataFormatAction.ts | 16 +++ .../Actions/IDatabaseDataSelectAction.ts | 1 + .../Actions/ResultSetFormatAction.ts | 49 ++++++++ .../Actions/ResultSetSelectAction.ts | 14 ++- .../src/IDataViewerPageState.ts | 1 + .../src/TableViewer/TableGrid.tsx | 14 +-- .../TableViewer/TableHeader/TableHeader.tsx | 2 +- .../TableHeader/TableWhereFilter.tsx | 7 -- .../PresentationTab.tsx | 15 ++- .../TablePresentationBar.tsx} | 29 +++-- .../src/TableViewer/TableViewer.tsx | 119 +++++++++++++----- .../src/TableViewer/ValuePanel.tsx | 73 +++++++++++ .../packages/plugin-data-viewer/src/index.ts | 2 + .../plugin-data-viewer/src/locales/en.ts | 1 + .../plugin-data-viewer/src/locales/ru.ts | 1 + .../plugin-data-viewer/src/manifest.ts | 11 +- .../src/IObjectViewerTabState.ts | 2 +- .../src/ObjectPage/DBObjectPageService.ts | 6 +- .../src/ObjectPage/ObjectPage.ts | 4 +- .../src/ObjectViewerTabService.ts | 6 +- .../src/SqlResultTabs/SqlResultPanel.tsx | 3 + 39 files changed, 542 insertions(+), 154 deletions(-) create mode 100644 webapp/packages/plugin-data-viewer/public/icons/text_value_presentation.png create mode 100644 webapp/packages/plugin-data-viewer/src/DataPresentation/TextValuePresentation/TextValuePresentation.tsx create mode 100644 webapp/packages/plugin-data-viewer/src/DataPresentation/TextValuePresentation/TextValuePresentationBootstrap.ts create mode 100644 webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataFormatAction.ts create mode 100644 webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSetFormatAction.ts rename webapp/packages/plugin-data-viewer/src/TableViewer/{TableLeftBar => TablePresentationBar}/PresentationTab.tsx (72%) rename webapp/packages/plugin-data-viewer/src/TableViewer/{TableLeftBar/TableLeftBar.tsx => TablePresentationBar/TablePresentationBar.tsx} (84%) create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel.tsx diff --git a/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx b/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx index 9fa6016c80..baa60739bc 100644 --- a/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import { useCallback, useContext } from 'react'; import styled, { css, use } from 'reshadow'; -import { useStyles } from '@cloudbeaver/core-theming'; +import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming'; import type { ILayoutSizeProps } from '../Containers/LayoutProps'; import { baseFormControlStylesNew } from './baseFormControlStylesNew'; @@ -20,16 +20,33 @@ const styles = css` textarea { line-height: 19px; } + field[|embedded] { + height: 100%; + display: flex; + flex-direction: column; + + & textarea { + border-radius: 0 !important; + height: 100%; + resize: none !important; + } + } field-label { display: block; padding-bottom: 10px; composes: theme-typography--body1 from global; font-weight: 500; + + &:empty { + display: none; + } } `; -type BaseProps = Omit, 'onChange'> & ILayoutSizeProps & { +type BaseProps = Omit, 'onChange' | 'style'> & ILayoutSizeProps & { mod?: 'surface'; + style?: ComponentStyle; + embedded?: boolean; }; type ControlledProps = BaseProps & { @@ -53,6 +70,7 @@ interface TextareaType { export const TextareaNew: TextareaType = observer(function TextareaNew({ name, + style, value: controlledValue, state, children, @@ -61,6 +79,7 @@ export const TextareaNew: TextareaType = observer(function TextareaNew({ medium, large, mod, + embedded, onChange = () => {}, ...rest }: ControlledProps | ObjectProps) { @@ -80,8 +99,8 @@ export const TextareaNew: TextareaType = observer(function TextareaNew({ const value = state ? state[name] : controlledValue; - return styled(useStyles(baseFormControlStylesNew, styles))( - + return styled(useStyles(baseFormControlStylesNew, styles, style))( + {children}