Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53fd036f58 |
No files matched your search
@@ -1,7 +0,0 @@
|
|||||||
name: build
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
jobs:
|
|
||||||
maven:
|
|
||||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
|
||||||
secrets: inherit
|
|
||||||
@@ -20,4 +20,4 @@ concurrency:
|
|||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@1c389210e6dfda1630d053c07ae4dae523dd5576
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
|
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
|
||||||
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
|
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
|
||||||
'release:rc'))
|
'release:rc'))
|
||||||
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@1c389210e6dfda1630d053c07ae4dae523dd5576
|
||||||
with:
|
with:
|
||||||
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
|
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -6,14 +6,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `SlackDeliveryDestination` now implements the QQQ 4.1 core delivery SPI (`com.kingsrook.qqq.backend.core.delivery`, qqq#1205) and the producer registers it as `slack` in the core `DeliveryDestinationsMetaData` registry; the stand-in SPI copy is removed.
|
|
||||||
- `SlackPayloadGuard` uses core `ClassifiedValues` for field classification and `ClassifiedValueTokens.changed()` for tokenized restricted values (qqq#1142), replacing its stand-in logic.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- **Install for my organization** and **Connect Slack** on the Connections table ended on an empty screen with only a Return button: the screen declared a view field but no component, and a frontend step shows only its components. The screen is now an HTML component with one sentence about what happens, a **Continue to Slack** link to the consent page (same tab) and the link's lifetime (10 minutes, `SlackOAuthState.LIFETIME_SECONDS`).
|
|
||||||
- After Slack, the callback redirected to `/preferences?section=slack`, which the host UI does not serve. It now returns to `/app/preferences?section=slack` with `&result=connected` or `&error=<code>`.
|
|
||||||
- Connecting in an environment without the public URL or the Slack app secrets failed with `IllegalArgumentException (Slack public URL is required)` or `Slack secret unavailable`. It is now refused with a user-facing message: "Slack is not set up in this environment", which of the two is missing, and the names of the missing environment variables, never a value.
|
|
||||||
- QBit metadata now reports the Maven artifact version, including release versions, from a filtered properties resource.
|
- QBit metadata now reports the Maven artifact version, including release versions, from a filtered properties resource.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+5
-16
@@ -121,12 +121,7 @@ There is one row per QQQ user and workspace row, unique (`userId`, `workspaceId`
|
|||||||
|
|
||||||
1. **Start** (UI API only). It's a preference-section action, **Connect Slack**, in the user's Slack
|
1. **Start** (UI API only). It's a preference-section action, **Connect Slack**, in the user's Slack
|
||||||
section (core #1162), or the same action on the Connections table.
|
section (core #1162), or the same action on the Connections table.
|
||||||
- It returns the Slack authorize URL. Nobody copies it: the preference action answers with the address
|
- It returns the Slack authorize URL.
|
||||||
alone, which the UI opens (see Preferences below), and the Connections table action ends on a screen
|
|
||||||
with one sentence, a **Continue to Slack** link and the link's lifetime.
|
|
||||||
- An environment without the public URL or without the Slack app secrets is refused before any state is
|
|
||||||
issued: "Slack is not set up in this environment", which of the two is missing, and the names of the
|
|
||||||
missing environment variables (never a value).
|
|
||||||
- The `state` is a server-signed, single-use token over the user id, tenant key, nonce, mode
|
- The `state` is a server-signed, single-use token over the user id, tenant key, nonce, mode
|
||||||
(`user` or `user+bot`) and a 10-minute expiry.
|
(`user` or `user+bot`) and a 10-minute expiry.
|
||||||
- Users who hold `slack.workspace.install` in their tenant may also request the bot scopes.
|
- Users who hold `slack.workspace.install` in their tenant may also request the bot scopes.
|
||||||
@@ -137,11 +132,8 @@ There is one row per QQQ user and workspace row, unique (`userId`, `workspaceId`
|
|||||||
environment variables.
|
environment variables.
|
||||||
- It upserts the tenant's `slack_workspace` (bot token, when granted) and the user's `slack_connection`
|
- It upserts the tenant's `slack_workspace` (bot token, when granted) and the user's `slack_connection`
|
||||||
(user token and Slack user id).
|
(user token and Slack user id).
|
||||||
- Then it redirects to the Slack section of the Preferences page,
|
- Then it redirects to the Preferences page with a result notice.
|
||||||
`/app/preferences?section=slack&result=connected`, which says the connection succeeded.
|
- Errors redirect with a plain message, never token text.
|
||||||
- Errors redirect there with a fixed code, `&error=<code>` (`sign_in_required`, `not_permitted`,
|
|
||||||
`authorization_denied`, `exchange_failed`, `connect_failed`), never provider or token text. The page
|
|
||||||
shows the code as words.
|
|
||||||
3. **Disconnect** calls `auth.revoke` on the user token, then marks the row REVOKED and clears the token.
|
3. **Disconnect** calls `auth.revoke` on the user token, then marks the row REVOKED and clears the token.
|
||||||
**Uninstall** (admin) calls `apps.uninstall` or revokes the bot token, then marks the workspace
|
**Uninstall** (admin) calls `apps.uninstall` or revokes the bot token, then marks the workspace
|
||||||
UNINSTALLED.
|
UNINSTALLED.
|
||||||
@@ -548,11 +540,8 @@ qbit-slack's own Preferences section (core #1162) has:
|
|||||||
The section requires `slack.connect`, appears in the user menu, and rejects PATCH requests.
|
The section requires `slack.connect`, appears in the user menu, and rejects PATCH requests.
|
||||||
Actions are filtered per session; test messages also require `slack.send`. RC.7 has no action-level
|
Actions are filtered per session; test messages also require `slack.send`. RC.7 has no action-level
|
||||||
permission property, so the section returns a filtered metadata view without mutating its shared
|
permission property, so the section returns a filtered metadata view without mutating its shared
|
||||||
registrations. Connect and Install return their authorize URL as the whole of
|
registrations. Connect and Install return their authorize URL in `UserPreferenceActionResult.message`;
|
||||||
`UserPreferenceActionResult.message`: core has no external-link or redirect result field (checked through
|
RC.7 has no external-link or redirect result field, so this does not automatically navigate the client.
|
||||||
QQQ 4.1.0-RC.31). qqq-frontend-next sends the browser to a message that is only an `https:` address
|
|
||||||
instead of showing it, so the message must stay the address and nothing else. A typed redirect field in
|
|
||||||
core would replace this convention.
|
|
||||||
|
|
||||||
Without qbit-alerts, callers use `SendMessageAction` (`slack`, `me` or `user:<id>`) directly.
|
Without qbit-alerts, callers use `SendMessageAction` (`slack`, `me` or `user:<id>`) directly.
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
|
|
||||||
<revision>0.1.0-SNAPSHOT</revision>
|
<revision>0.1.0-SNAPSHOT</revision>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.kingsrook</groupId>
|
<groupId>com.kingsrook</groupId>
|
||||||
<artifactId>qbit-build-parent</artifactId>
|
<artifactId>qbit-build-parent</artifactId>
|
||||||
<version>2.2.1</version>
|
<version>2.1.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.kingsrook.qbits</groupId>
|
<groupId>com.kingsrook.qbits</groupId>
|
||||||
@@ -78,7 +77,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.kingsrook.qqq</groupId>
|
<groupId>com.kingsrook.qqq</groupId>
|
||||||
<artifactId>qqq-frontend-next</artifactId>
|
<artifactId>qqq-frontend-next</artifactId>
|
||||||
<version>1.0.0-RC.38</version>
|
<version>1.0.0-RC.19</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -242,7 +241,7 @@
|
|||||||
<activeByDefault>true</activeByDefault>
|
<activeByDefault>true</activeByDefault>
|
||||||
</activation>
|
</activation>
|
||||||
<properties>
|
<properties>
|
||||||
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
|
<qqq.snapshot.version>4.1.0-RC.7</qqq.snapshot.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public class SlackQBitProducer implements QBitMetaDataProducer<SlackQBitConfig>
|
|||||||
output.add(new SlackOutboxMessagingProviderMetaData().withName(qBitConfig.getBridgeProviderName()));
|
output.add(new SlackOutboxMessagingProviderMetaData().withName(qBitConfig.getBridgeProviderName()));
|
||||||
|
|
||||||
output.addSelfToInstance(qInstance);
|
output.addSelfToInstance(qInstance);
|
||||||
com.kingsrook.qbits.slack.delivery.SlackDeliveryDestination.register(qInstance, getQBitConfig());
|
com.kingsrook.qbits.slack.delivery.SlackDeliveryDestination.register();
|
||||||
qInstance.addApp(com.kingsrook.qbits.slack.app.SlackApp.app(qInstance.getTables()));
|
qInstance.addApp(com.kingsrook.qbits.slack.app.SlackApp.app(qInstance.getTables()));
|
||||||
com.kingsrook.qbits.slack.app.SlackApp.widgets().forEach(qInstance::addWidget);
|
com.kingsrook.qbits.slack.app.SlackApp.widgets().forEach(qInstance::addWidget);
|
||||||
if(isPreferencesAvailable())
|
if(isPreferencesAvailable())
|
||||||
|
|||||||
@@ -26,9 +26,15 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import com.kingsrook.qbits.slack.SlackQBitConfig;
|
|
||||||
import com.kingsrook.qbits.slack.api.SlackApi;
|
import com.kingsrook.qbits.slack.api.SlackApi;
|
||||||
import com.kingsrook.qbits.slack.api.SlackApiMessage;
|
import com.kingsrook.qbits.slack.api.SlackApiMessage;
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryDestinationContext; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryDestinationInterface; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryRequest; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryResult; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryTargetOption; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryTargetStep; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.QDeliveryDestinationMetaData; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
||||||
import com.kingsrook.qbits.slack.outbox.SlackSendRequest;
|
import com.kingsrook.qbits.slack.outbox.SlackSendRequest;
|
||||||
import com.kingsrook.qbits.slack.outbox.SlackSendResult;
|
import com.kingsrook.qbits.slack.outbox.SlackSendResult;
|
||||||
@@ -39,72 +45,45 @@ import com.kingsrook.qbits.slack.support.SlackRuntime;
|
|||||||
import com.kingsrook.qbits.slack.support.SlackStore;
|
import com.kingsrook.qbits.slack.support.SlackStore;
|
||||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryDestinationContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryDestinationInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryDestinationsMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryRequest;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryResult;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryTargetOption;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryTargetStep;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.QDeliveryDestinationMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Slack output destination for the QQQ core delivery SPI
|
** Host-callable Slack output destination until core's delivery SPI is released.
|
||||||
** (DeliveryDestinationInterface). The producer registers it in the core
|
** Targets are workspaceId and where (me or a channel address from SlackApi).
|
||||||
** DeliveryDestinationsMetaData registry, so hosts that install core delivery
|
|
||||||
** offer it in their pickers. Targets are workspaceId and where (me or a
|
|
||||||
** channel address from SlackApi).
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class SlackDeliveryDestination implements DeliveryDestinationInterface
|
public class SlackDeliveryDestination implements DeliveryDestinationInterface
|
||||||
{
|
{
|
||||||
public static final String NAME = "slack";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Describe the policy core applies before invoking this destination, using
|
** Describe the policy hosts must apply before invoking this destination.
|
||||||
** the Slack config of the current instance.
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QDeliveryDestinationMetaData metadata()
|
public QDeliveryDestinationMetaData metadata()
|
||||||
{
|
{
|
||||||
return (metadata(SlackRuntime.config()));
|
return (new QDeliveryDestinationMetaData().withName("slack").withLabel("Slack")
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Describe the policy core applies before invoking this destination.
|
|
||||||
*******************************************************************************/
|
|
||||||
public static QDeliveryDestinationMetaData metadata(SlackQBitConfig config)
|
|
||||||
{
|
|
||||||
return (new QDeliveryDestinationMetaData().withName(NAME).withLabel("Slack")
|
|
||||||
.withPermissionName("slack.send").withCodeReference(new QCodeReference(SlackDeliveryDestination.class))
|
.withPermissionName("slack.send").withCodeReference(new QCodeReference(SlackDeliveryDestination.class))
|
||||||
.withMaxClassification(DataClassification.CONFIDENTIAL)
|
.withMaxClassification(DataClassification.CONFIDENTIAL)
|
||||||
.withAboveMaxClassification(QDeliveryDestinationMetaData.AboveMaxClassification.REFUSE)
|
.withAboveMaxClassification(QDeliveryDestinationMetaData.AboveMaxClassification.REFUSE)
|
||||||
.withMaxAttachmentBytes(config.getMaxAttachmentBytes()).withAllowBearerLinks(false));
|
.withMaxAttachmentBytes(SlackRuntime.config().getMaxAttachmentBytes()).withAllowBearerLinks(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Register with the core delivery registry; production may run repeatedly,
|
** Detect the real SPI without registering an incompatible temporary instance.
|
||||||
** and a destination already registered under this name is kept.
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void register(QInstance instance, SlackQBitConfig config)
|
public static void register()
|
||||||
{
|
{
|
||||||
DeliveryDestinationsMetaData registry = DeliveryDestinationsMetaData.ofOrWithNew(instance);
|
try
|
||||||
if(registry.getDestinations().stream().noneMatch(destination -> NAME.equals(destination.getName())))
|
|
||||||
{
|
{
|
||||||
registry.withDestination(metadata(config));
|
Class.forName("com.kingsrook.qqq.backend.core.delivery.DeliveryDestinationsMetaData");
|
||||||
|
com.kingsrook.qqq.backend.core.logging.QLogger.getLogger(SlackDeliveryDestination.class)
|
||||||
|
.info("Slack delivery stand-in will be replaced after RC.8; host registration is deferred"); // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
// RC.6 hosts use this destination directly.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Offer only active connections and membership-checked targets of this user.
|
** Offer only active connections and membership-checked targets of this user.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** DeliveryAttachment.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DeliveryAttachment implements java.io.Serializable
|
||||||
|
{
|
||||||
|
private String fileName;
|
||||||
|
private String mimeType;
|
||||||
|
private Long size;
|
||||||
|
private String storageTableName;
|
||||||
|
private String storageReference;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get fileName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getFileName()
|
||||||
|
{
|
||||||
|
return (fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set fileName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFileName(String fileName)
|
||||||
|
{
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With fileName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryAttachment withFileName(String fileName)
|
||||||
|
{
|
||||||
|
this.fileName = fileName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get mimeType.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getMimeType()
|
||||||
|
{
|
||||||
|
return (mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set mimeType.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMimeType(String mimeType)
|
||||||
|
{
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With mimeType.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryAttachment withMimeType(String mimeType)
|
||||||
|
{
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get size.
|
||||||
|
*******************************************************************************/
|
||||||
|
public Long getSize()
|
||||||
|
{
|
||||||
|
return (size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set size.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSize(Long size)
|
||||||
|
{
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With size.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryAttachment withSize(Long size)
|
||||||
|
{
|
||||||
|
this.size = size;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get storageTableName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getStorageTableName()
|
||||||
|
{
|
||||||
|
return (storageTableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set storageTableName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setStorageTableName(String storageTableName)
|
||||||
|
{
|
||||||
|
this.storageTableName = storageTableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With storageTableName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryAttachment withStorageTableName(String storageTableName)
|
||||||
|
{
|
||||||
|
this.storageTableName = storageTableName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get storageReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getStorageReference()
|
||||||
|
{
|
||||||
|
return (storageReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set storageReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setStorageReference(String storageReference)
|
||||||
|
{
|
||||||
|
this.storageReference = storageReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With storageReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryAttachment withStorageReference(String storageReference)
|
||||||
|
{
|
||||||
|
this.storageReference = storageReference;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** DeliveryDestinationContext.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DeliveryDestinationContext implements java.io.Serializable
|
||||||
|
{
|
||||||
|
private QDeliveryDestinationMetaData destination;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get destination.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData getDestination()
|
||||||
|
{
|
||||||
|
return (destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set destination.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setDestination(QDeliveryDestinationMetaData destination)
|
||||||
|
{
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With destination.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryDestinationContext withDestination(QDeliveryDestinationMetaData destination)
|
||||||
|
{
|
||||||
|
this.destination = destination;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Owner-scoped output delivery provider.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface DeliveryDestinationInterface
|
||||||
|
{
|
||||||
|
/*******************************************************************************
|
||||||
|
** Describe the next target in the current owner session.
|
||||||
|
*******************************************************************************/
|
||||||
|
DeliveryTargetStep describeTargets(DeliveryDestinationContext context, Map<String, Serializable> chosen) throws QException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Validate a complete target before sending.
|
||||||
|
*******************************************************************************/
|
||||||
|
default List<String> validate(DeliveryRequest request) throws QException
|
||||||
|
{
|
||||||
|
return (List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Deliver idempotently using the request key.
|
||||||
|
*******************************************************************************/
|
||||||
|
DeliveryResult deliver(DeliveryRequest request) throws QException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Reconcile a previous delivery when supported.
|
||||||
|
*******************************************************************************/
|
||||||
|
default Optional<DeliveryResult> lookup(String idempotencyKey) throws QException
|
||||||
|
{
|
||||||
|
return (Optional.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** DeliveryRequest.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DeliveryRequest implements java.io.Serializable
|
||||||
|
{
|
||||||
|
private String destinationName;
|
||||||
|
private String ownerUserId;
|
||||||
|
private String sourceType;
|
||||||
|
private String sourceKey;
|
||||||
|
private String title;
|
||||||
|
private String summary;
|
||||||
|
private String link;
|
||||||
|
private String idempotencyKey;
|
||||||
|
private Map<String, Serializable> targetValues = Map.of();
|
||||||
|
private List<DeliveryAttachment> attachments = List.of();
|
||||||
|
private DataClassification contentClassification;
|
||||||
|
private List<String> restrictedFieldLabels = List.of();
|
||||||
|
private boolean leaveOutRestricted;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get destinationName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getDestinationName()
|
||||||
|
{
|
||||||
|
return (destinationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set destinationName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setDestinationName(String destinationName)
|
||||||
|
{
|
||||||
|
this.destinationName = destinationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With destinationName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withDestinationName(String destinationName)
|
||||||
|
{
|
||||||
|
this.destinationName = destinationName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get ownerUserId.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getOwnerUserId()
|
||||||
|
{
|
||||||
|
return (ownerUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set ownerUserId.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOwnerUserId(String ownerUserId)
|
||||||
|
{
|
||||||
|
this.ownerUserId = ownerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With ownerUserId.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withOwnerUserId(String ownerUserId)
|
||||||
|
{
|
||||||
|
this.ownerUserId = ownerUserId;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get sourceType.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSourceType()
|
||||||
|
{
|
||||||
|
return (sourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set sourceType.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSourceType(String sourceType)
|
||||||
|
{
|
||||||
|
this.sourceType = sourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With sourceType.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withSourceType(String sourceType)
|
||||||
|
{
|
||||||
|
this.sourceType = sourceType;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get sourceKey.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSourceKey()
|
||||||
|
{
|
||||||
|
return (sourceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set sourceKey.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSourceKey(String sourceKey)
|
||||||
|
{
|
||||||
|
this.sourceKey = sourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With sourceKey.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withSourceKey(String sourceKey)
|
||||||
|
{
|
||||||
|
this.sourceKey = sourceKey;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get title.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getTitle()
|
||||||
|
{
|
||||||
|
return (title);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set title.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setTitle(String title)
|
||||||
|
{
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With title.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withTitle(String title)
|
||||||
|
{
|
||||||
|
this.title = title;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get summary.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSummary()
|
||||||
|
{
|
||||||
|
return (summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set summary.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSummary(String summary)
|
||||||
|
{
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With summary.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withSummary(String summary)
|
||||||
|
{
|
||||||
|
this.summary = summary;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get link.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLink()
|
||||||
|
{
|
||||||
|
return (link);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set link.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLink(String link)
|
||||||
|
{
|
||||||
|
this.link = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With link.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withLink(String link)
|
||||||
|
{
|
||||||
|
this.link = link;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get idempotencyKey.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getIdempotencyKey()
|
||||||
|
{
|
||||||
|
return (idempotencyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set idempotencyKey.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setIdempotencyKey(String idempotencyKey)
|
||||||
|
{
|
||||||
|
this.idempotencyKey = idempotencyKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With idempotencyKey.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withIdempotencyKey(String idempotencyKey)
|
||||||
|
{
|
||||||
|
this.idempotencyKey = idempotencyKey;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get targetValues.
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> getTargetValues()
|
||||||
|
{
|
||||||
|
return (targetValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set targetValues.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setTargetValues(Map<String, Serializable> targetValues)
|
||||||
|
{
|
||||||
|
this.targetValues = targetValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With targetValues.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withTargetValues(Map<String, Serializable> targetValues)
|
||||||
|
{
|
||||||
|
this.targetValues = targetValues;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get attachments.
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<DeliveryAttachment> getAttachments()
|
||||||
|
{
|
||||||
|
return (attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set attachments.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAttachments(List<DeliveryAttachment> attachments)
|
||||||
|
{
|
||||||
|
this.attachments = attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With attachments.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withAttachments(List<DeliveryAttachment> attachments)
|
||||||
|
{
|
||||||
|
this.attachments = attachments;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get contentClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DataClassification getContentClassification()
|
||||||
|
{
|
||||||
|
return (contentClassification);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set contentClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setContentClassification(DataClassification contentClassification)
|
||||||
|
{
|
||||||
|
this.contentClassification = contentClassification;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With contentClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withContentClassification(DataClassification contentClassification)
|
||||||
|
{
|
||||||
|
this.contentClassification = contentClassification;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get restrictedFieldLabels.
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<String> getRestrictedFieldLabels()
|
||||||
|
{
|
||||||
|
return (restrictedFieldLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set restrictedFieldLabels.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRestrictedFieldLabels(List<String> restrictedFieldLabels)
|
||||||
|
{
|
||||||
|
this.restrictedFieldLabels = restrictedFieldLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With restrictedFieldLabels.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withRestrictedFieldLabels(List<String> restrictedFieldLabels)
|
||||||
|
{
|
||||||
|
this.restrictedFieldLabels = restrictedFieldLabels;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get leaveOutRestricted.
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getLeaveOutRestricted()
|
||||||
|
{
|
||||||
|
return (leaveOutRestricted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set leaveOutRestricted.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLeaveOutRestricted(boolean leaveOutRestricted)
|
||||||
|
{
|
||||||
|
this.leaveOutRestricted = leaveOutRestricted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With leaveOutRestricted.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryRequest withLeaveOutRestricted(boolean leaveOutRestricted)
|
||||||
|
{
|
||||||
|
this.leaveOutRestricted = leaveOutRestricted;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** DeliveryResult.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DeliveryResult implements java.io.Serializable
|
||||||
|
{
|
||||||
|
/*******************************************************************************
|
||||||
|
** Recorded delivery outcome.
|
||||||
|
*******************************************************************************/
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
public enum Status
|
||||||
|
{
|
||||||
|
DELIVERED, QUEUED, REFUSED, FAILED_RETRYABLE, FAILED, UNAUTHORIZED
|
||||||
|
}
|
||||||
|
|
||||||
|
private Status status;
|
||||||
|
private String externalReference;
|
||||||
|
private List<String> reasons = List.of();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get status.
|
||||||
|
*******************************************************************************/
|
||||||
|
public Status getStatus()
|
||||||
|
{
|
||||||
|
return (status);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set status.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setStatus(Status status)
|
||||||
|
{
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With status.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryResult withStatus(Status status)
|
||||||
|
{
|
||||||
|
this.status = status;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get externalReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getExternalReference()
|
||||||
|
{
|
||||||
|
return (externalReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set externalReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setExternalReference(String externalReference)
|
||||||
|
{
|
||||||
|
this.externalReference = externalReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With externalReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryResult withExternalReference(String externalReference)
|
||||||
|
{
|
||||||
|
this.externalReference = externalReference;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get reasons.
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<String> getReasons()
|
||||||
|
{
|
||||||
|
return (reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set reasons.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setReasons(List<String> reasons)
|
||||||
|
{
|
||||||
|
this.reasons = reasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With reasons.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryResult withReasons(List<String> reasons)
|
||||||
|
{
|
||||||
|
this.reasons = reasons;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** DeliveryTargetOption.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DeliveryTargetOption implements java.io.Serializable
|
||||||
|
{
|
||||||
|
private String value;
|
||||||
|
private String label;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get value.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set value.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setValue(String value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With value.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetOption withValue(String value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel()
|
||||||
|
{
|
||||||
|
return (label);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetOption withLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get description.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return (description);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set description.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setDescription(String description)
|
||||||
|
{
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With description.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetOption withDescription(String description)
|
||||||
|
{
|
||||||
|
this.description = description;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** DeliveryTargetStep.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DeliveryTargetStep implements java.io.Serializable
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
private String label;
|
||||||
|
private List<DeliveryTargetOption> options = List.of();
|
||||||
|
private boolean freeText;
|
||||||
|
private boolean complete;
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get name.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return (name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set name.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With name.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetStep withName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel()
|
||||||
|
{
|
||||||
|
return (label);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetStep withLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get options.
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<DeliveryTargetOption> getOptions()
|
||||||
|
{
|
||||||
|
return (options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set options.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOptions(List<DeliveryTargetOption> options)
|
||||||
|
{
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With options.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetStep withOptions(List<DeliveryTargetOption> options)
|
||||||
|
{
|
||||||
|
this.options = options;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get freeText.
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getFreeText()
|
||||||
|
{
|
||||||
|
return (freeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set freeText.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFreeText(boolean freeText)
|
||||||
|
{
|
||||||
|
this.freeText = freeText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With freeText.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetStep withFreeText(boolean freeText)
|
||||||
|
{
|
||||||
|
this.freeText = freeText;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get complete.
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getComplete()
|
||||||
|
{
|
||||||
|
return (complete);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set complete.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setComplete(boolean complete)
|
||||||
|
{
|
||||||
|
this.complete = complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With complete.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetStep withComplete(boolean complete)
|
||||||
|
{
|
||||||
|
this.complete = complete;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get summary.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSummary()
|
||||||
|
{
|
||||||
|
return (summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set summary.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSummary(String summary)
|
||||||
|
{
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With summary.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DeliveryTargetStep withSummary(String summary)
|
||||||
|
{
|
||||||
|
this.summary = summary;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
+396
@@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.kingsrook.qbits.slack.delivery.standin; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** QDeliveryDestinationMetaData.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QDeliveryDestinationMetaData implements java.io.Serializable
|
||||||
|
{
|
||||||
|
/*******************************************************************************
|
||||||
|
** Policy for content above the destination ceiling.
|
||||||
|
*******************************************************************************/
|
||||||
|
// STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
public enum AboveMaxClassification
|
||||||
|
{
|
||||||
|
REFUSE, LINK_ONLY
|
||||||
|
}
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String label;
|
||||||
|
private String icon;
|
||||||
|
private String permissionName;
|
||||||
|
private String emailProviderName;
|
||||||
|
private Integer order = 0;
|
||||||
|
private QCodeReference codeReference;
|
||||||
|
private DataClassification maxClassification = DataClassification.PUBLIC;
|
||||||
|
private Long maxAttachmentBytes;
|
||||||
|
private boolean allowBearerLinks;
|
||||||
|
private AboveMaxClassification aboveMaxClassification = AboveMaxClassification.REFUSE;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get name.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return (name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set name.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With name.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel()
|
||||||
|
{
|
||||||
|
return (label);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With label.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get icon.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getIcon()
|
||||||
|
{
|
||||||
|
return (icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set icon.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setIcon(String icon)
|
||||||
|
{
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With icon.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withIcon(String icon)
|
||||||
|
{
|
||||||
|
this.icon = icon;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get permissionName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getPermissionName()
|
||||||
|
{
|
||||||
|
return (permissionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set permissionName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setPermissionName(String permissionName)
|
||||||
|
{
|
||||||
|
this.permissionName = permissionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With permissionName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withPermissionName(String permissionName)
|
||||||
|
{
|
||||||
|
this.permissionName = permissionName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get emailProviderName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getEmailProviderName()
|
||||||
|
{
|
||||||
|
return (emailProviderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set emailProviderName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setEmailProviderName(String emailProviderName)
|
||||||
|
{
|
||||||
|
this.emailProviderName = emailProviderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With emailProviderName.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withEmailProviderName(String emailProviderName)
|
||||||
|
{
|
||||||
|
this.emailProviderName = emailProviderName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get order.
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getOrder()
|
||||||
|
{
|
||||||
|
return (order);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set order.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOrder(Integer order)
|
||||||
|
{
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With order.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withOrder(Integer order)
|
||||||
|
{
|
||||||
|
this.order = order;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get codeReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getCodeReference()
|
||||||
|
{
|
||||||
|
return (codeReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set codeReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCodeReference(QCodeReference codeReference)
|
||||||
|
{
|
||||||
|
this.codeReference = codeReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With codeReference.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withCodeReference(QCodeReference codeReference)
|
||||||
|
{
|
||||||
|
this.codeReference = codeReference;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get maxClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public DataClassification getMaxClassification()
|
||||||
|
{
|
||||||
|
return (maxClassification);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set maxClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMaxClassification(DataClassification maxClassification)
|
||||||
|
{
|
||||||
|
this.maxClassification = maxClassification;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With maxClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withMaxClassification(DataClassification maxClassification)
|
||||||
|
{
|
||||||
|
this.maxClassification = maxClassification;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get maxAttachmentBytes.
|
||||||
|
*******************************************************************************/
|
||||||
|
public Long getMaxAttachmentBytes()
|
||||||
|
{
|
||||||
|
return (maxAttachmentBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set maxAttachmentBytes.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMaxAttachmentBytes(Long maxAttachmentBytes)
|
||||||
|
{
|
||||||
|
this.maxAttachmentBytes = maxAttachmentBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With maxAttachmentBytes.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withMaxAttachmentBytes(Long maxAttachmentBytes)
|
||||||
|
{
|
||||||
|
this.maxAttachmentBytes = maxAttachmentBytes;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get allowBearerLinks.
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getAllowBearerLinks()
|
||||||
|
{
|
||||||
|
return (allowBearerLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set allowBearerLinks.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAllowBearerLinks(boolean allowBearerLinks)
|
||||||
|
{
|
||||||
|
this.allowBearerLinks = allowBearerLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With allowBearerLinks.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withAllowBearerLinks(boolean allowBearerLinks)
|
||||||
|
{
|
||||||
|
this.allowBearerLinks = allowBearerLinks;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get aboveMaxClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public AboveMaxClassification getAboveMaxClassification()
|
||||||
|
{
|
||||||
|
return (aboveMaxClassification);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set aboveMaxClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAboveMaxClassification(AboveMaxClassification aboveMaxClassification)
|
||||||
|
{
|
||||||
|
this.aboveMaxClassification = aboveMaxClassification;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With aboveMaxClassification.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QDeliveryDestinationMetaData withAboveMaxClassification(AboveMaxClassification aboveMaxClassification)
|
||||||
|
{
|
||||||
|
this.aboveMaxClassification = aboveMaxClassification;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@ import java.net.URLEncoder;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Stream;
|
|
||||||
import com.kingsrook.qbits.slack.SlackQBitConfig;
|
import com.kingsrook.qbits.slack.SlackQBitConfig;
|
||||||
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
||||||
import com.kingsrook.qbits.slack.security.SlackSecrets;
|
import com.kingsrook.qbits.slack.security.SlackSecrets;
|
||||||
@@ -36,7 +35,6 @@ import com.kingsrook.qbits.slack.support.SlackStore;
|
|||||||
import com.kingsrook.qbits.slack.transport.SlackResponse;
|
import com.kingsrook.qbits.slack.transport.SlackResponse;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
@@ -61,10 +59,8 @@ public final class SlackConnect
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Start only for the signed-in user, never an automated or system session.
|
** Start only for the signed-in user, never an automated or system session.
|
||||||
** An environment without a public URL or without the Slack app secrets is
|
|
||||||
** refused in words the user can act on, before any state is issued.
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static String startUrl(String userId, String mode) throws QException
|
public static String startUrl(String userId, String mode)
|
||||||
{
|
{
|
||||||
QSession session = session();
|
QSession session = session();
|
||||||
if(!session.getUser().getIdReference().equals(userId) || !session.hasPermission("slack.connect")
|
if(!session.getUser().getIdReference().equals(userId) || !session.hasPermission("slack.connect")
|
||||||
@@ -73,11 +69,6 @@ public final class SlackConnect
|
|||||||
throw (new IllegalArgumentException("Slack connect is not permitted"));
|
throw (new IllegalArgumentException("Slack connect is not permitted"));
|
||||||
}
|
}
|
||||||
SlackQBitConfig config = SlackRuntime.config();
|
SlackQBitConfig config = SlackRuntime.config();
|
||||||
String notSetUp = notSetUp(config);
|
|
||||||
if(notSetUp != null)
|
|
||||||
{
|
|
||||||
throw (new QUserFacingException(notSetUp));
|
|
||||||
}
|
|
||||||
String redirect = redirectUri(config);
|
String redirect = redirectUri(config);
|
||||||
String state = SlackOAuthState.issue(config, session, userId, tenant(config, session), mode);
|
String state = SlackOAuthState.issue(config, session, userId, tenant(config, session), mode);
|
||||||
return ("https://slack.com/oauth/v2/authorize?client_id=" + encode(SlackSecrets.get(config, config.getClientIdEnvName()))
|
return ("https://slack.com/oauth/v2/authorize?client_id=" + encode(SlackSecrets.get(config, config.getClientIdEnvName()))
|
||||||
@@ -283,52 +274,14 @@ public final class SlackConnect
|
|||||||
return (session);
|
return (session);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Why Slack cannot be connected in this environment, or null when it can.
|
|
||||||
** Connecting needs two things from the host: the public URL Slack sends the
|
|
||||||
** browser back to, and the Slack app secrets (client id, client secret and
|
|
||||||
** token key). The sentence says which is missing and names the environment
|
|
||||||
** variables, never a value.
|
|
||||||
*******************************************************************************/
|
|
||||||
public static String notSetUp(SlackQBitConfig config)
|
|
||||||
{
|
|
||||||
boolean noUrl = config.getPublicBaseUrl() == null || config.getPublicBaseUrl().isBlank();
|
|
||||||
List<String> secrets = Stream.of(config.getClientIdEnvName(), config.getClientSecretEnvName(), config.getTokenKeyEnvName())
|
|
||||||
.filter(name -> !present(config, name)).toList();
|
|
||||||
if(!noUrl && secrets.isEmpty())
|
|
||||||
{
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
String missing = noUrl && !secrets.isEmpty() ? "the public URL and the Slack app secrets are not configured"
|
|
||||||
: noUrl ? "the public URL is not configured" : "the Slack app secrets are not configured";
|
|
||||||
return ("Slack is not set up in this environment: " + missing
|
|
||||||
+ (secrets.isEmpty() ? "." : " (missing " + String.join(", ", secrets) + ")."));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** True when a named secret resolves to a value. The value is not kept.
|
|
||||||
*******************************************************************************/
|
|
||||||
private static boolean present(SlackQBitConfig config, String name)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SlackSecrets.get(config, name);
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException e)
|
|
||||||
{
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** The exact redirect URI shared by start and exchange.
|
** The exact redirect URI shared by start and exchange.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static String redirectUri(SlackQBitConfig config)
|
private static String redirectUri(SlackQBitConfig config)
|
||||||
{
|
{
|
||||||
if(config.getPublicBaseUrl() == null || config.getPublicBaseUrl().isBlank())
|
if(config.getPublicBaseUrl() == null)
|
||||||
{
|
{
|
||||||
throw (new IllegalArgumentException("Slack is not set up in this environment: the public URL is not configured."));
|
throw (new IllegalArgumentException("Slack public URL is required"));
|
||||||
}
|
}
|
||||||
return (config.getPublicBaseUrl().replaceAll("/+$", "") + config.getOauthCallbackPath());
|
return (config.getPublicBaseUrl().replaceAll("/+$", "") + config.getOauthCallbackPath());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ import io.javalin.http.Context;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class SlackOAuthRouteProvider implements QJavalinRouteProviderInterface
|
public class SlackOAuthRouteProvider implements QJavalinRouteProviderInterface
|
||||||
{
|
{
|
||||||
/** The Slack section of the host UI's Preferences page. */
|
|
||||||
public static final String RETURN_PATH = "/app/preferences?section=slack";
|
|
||||||
|
|
||||||
private QInstance instance;
|
private QInstance instance;
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@@ -140,18 +137,7 @@ public class SlackOAuthRouteProvider implements QJavalinRouteProviderInterface
|
|||||||
QContext.init(previous);
|
QContext.init(previous);
|
||||||
}
|
}
|
||||||
context.header("Cache-Control", "no-store").header("Referrer-Policy", "no-referrer");
|
context.header("Cache-Control", "no-store").header("Referrer-Policy", "no-referrer");
|
||||||
context.redirect(returnUrl(SlackRuntime.config(instance).getPublicBaseUrl(), error));
|
context.redirect(SlackRuntime.config(instance).getPublicBaseUrl().replaceAll("/+$", "")
|
||||||
}
|
+ "/preferences?section=slack" + (error == null ? "&result=connected" : "&error=" + error));
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Where the browser lands after Slack: the user's Slack preferences in the
|
|
||||||
** host UI (/app/preferences, like every other /app path this qbit links to),
|
|
||||||
** with a fixed outcome code the page turns into words. A host without a
|
|
||||||
** public URL gets a path on the same origin.
|
|
||||||
*******************************************************************************/
|
|
||||||
public static String returnUrl(String publicBaseUrl, String error)
|
|
||||||
{
|
|
||||||
return ((publicBaseUrl == null ? "" : publicBaseUrl.replaceAll("/+$", ""))
|
|
||||||
+ RETURN_PATH + (error == null ? "&result=connected" : "&error=" + error));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,9 +43,6 @@ import org.json.JSONObject;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public final class SlackOAuthState
|
public final class SlackOAuthState
|
||||||
{
|
{
|
||||||
/** How long an issued state, and so the authorize address that carries it, stays valid. */
|
|
||||||
public static final int LIFETIME_SECONDS = 600;
|
|
||||||
|
|
||||||
private static final Map<String, Long> NONCES = new HashMap<>();
|
private static final Map<String, Long> NONCES = new HashMap<>();
|
||||||
private static final int CAPACITY = 10_000;
|
private static final int CAPACITY = 10_000;
|
||||||
|
|
||||||
@@ -75,7 +72,7 @@ public final class SlackOAuthState
|
|||||||
throw (new IllegalArgumentException("Slack connect unavailable"));
|
throw (new IllegalArgumentException("Slack connect unavailable"));
|
||||||
}
|
}
|
||||||
String nonce = UUID.randomUUID().toString();
|
String nonce = UUID.randomUUID().toString();
|
||||||
long expiry = now + LIFETIME_SECONDS;
|
long expiry = now + 600;
|
||||||
String payload = encode(new JSONObject().put("user", user).put("tenant", Objects.toString(tenant, ""))
|
String payload = encode(new JSONObject().put("user", user).put("tenant", Objects.toString(tenant, ""))
|
||||||
.put("session", session.getUuid()).put("nonce", nonce).put("mode", mode).put("expiry", expiry).toString().getBytes(StandardCharsets.UTF_8));
|
.put("session", session.getUuid()).put("nonce", nonce).put("mode", mode).put("expiry", expiry).toString().getBytes(StandardCharsets.UTF_8));
|
||||||
String signed = payload + "." + encode(sign(config, payload));
|
String signed = payload + "." + encode(sign(config, payload));
|
||||||
|
|||||||
@@ -27,16 +27,15 @@ import java.util.Map;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import com.kingsrook.qbits.slack.support.SlackRuntime;
|
import com.kingsrook.qbits.slack.support.SlackRuntime;
|
||||||
import com.kingsrook.qbits.slack.template.VariableSchema;
|
import com.kingsrook.qbits.slack.template.VariableSchema;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.ai.AiDataClassifications;
|
||||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValues;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Refuse restricted content before persistence. Field classification comes
|
** Refuse restricted content before persistence. Uses RC.6 field classifications.
|
||||||
** from core ClassifiedValues (which honors a host's audit-value protector);
|
** STAND-IN(core=QRun-IO/qqq#1142, removeBy=4.1.0-RC.8): ClassifiedValues and
|
||||||
** tokenized restricted values become core ClassifiedValueTokens.changed(), a
|
** ClassifiedValueTokens are absent in RC.6. Restricted tokens use changed:v1,
|
||||||
** deliberately non-reversible marker, never a hash of a low-entropy value.
|
** a deliberately non-reversible marker, never a hash of a low-entropy value.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public final class SlackPayloadGuard
|
public final class SlackPayloadGuard
|
||||||
{
|
{
|
||||||
@@ -89,10 +88,7 @@ public final class SlackPayloadGuard
|
|||||||
{
|
{
|
||||||
throw (new IllegalArgumentException("Unknown classified field"));
|
throw (new IllegalArgumentException("Unknown classified field"));
|
||||||
}
|
}
|
||||||
if(ClassifiedValues.isClassified(table, field))
|
classification = DataClassification.max(classification, AiDataClassifications.ofField(table, field));
|
||||||
{
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (classification != null && classification.isAtLeast(DataClassification.PII));
|
return (classification != null && classification.isAtLeast(DataClassification.PII));
|
||||||
}
|
}
|
||||||
@@ -127,7 +123,7 @@ public final class SlackPayloadGuard
|
|||||||
{
|
{
|
||||||
throw (new RestrictedVariableException(declaration.name()));
|
throw (new RestrictedVariableException(declaration.name()));
|
||||||
}
|
}
|
||||||
safe.put(entry.getKey(), ClassifiedValueTokens.changed());
|
safe.put(entry.getKey(), "changed:v1");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -149,7 +145,7 @@ public final class SlackPayloadGuard
|
|||||||
{
|
{
|
||||||
throw (new RestrictedVariableException(declaration.name()));
|
throw (new RestrictedVariableException(declaration.name()));
|
||||||
}
|
}
|
||||||
safe.put(declaration.name(), ClassifiedValueTokens.changed());
|
safe.put(declaration.name(), "changed:v1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (safe);
|
return (safe);
|
||||||
@@ -172,7 +168,7 @@ public final class SlackPayloadGuard
|
|||||||
{
|
{
|
||||||
throw (new IllegalArgumentException("Restricted template variable"));
|
throw (new IllegalArgumentException("Restricted template variable"));
|
||||||
}
|
}
|
||||||
return (ClassifiedValueTokens.changed());
|
return ("changed:v1");
|
||||||
}
|
}
|
||||||
return (sanitize(classified.value(), tokenize));
|
return (sanitize(classified.value(), tokenize));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class SlackPreferences
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Start signed, single-use OAuth for the current user only.
|
** Start signed, single-use OAuth for the current user only.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static String connect(boolean install) throws QException
|
public static String connect(boolean install)
|
||||||
{
|
{
|
||||||
return (SlackConnect.startUrl(owner(), install ? "user+bot" : "user"));
|
return (SlackConnect.startUrl(owner(), install ? "user+bot" : "user"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,11 +162,7 @@ public class SlackPreferencesProvider implements UserPreferenceProviderInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Core has no redirect field on an action result (checked through QQQ
|
** RC.7 has no redirect result; return the authorize URL in its message field.
|
||||||
** 4.1.0-RC.31), so Connect and Install answer with the authorize address as
|
|
||||||
** the whole message and nothing else. qqq-frontend-next sends the browser to
|
|
||||||
** a message that is only an https address instead of showing it; any other
|
|
||||||
** word in the message would turn it back into text to read.
|
|
||||||
** Row services retain ownership checks even after core has checked the row.
|
** Row services retain ownership checks even after core has checked the row.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import com.kingsrook.qbits.slack.SlackQBitConfig;
|
|||||||
import com.kingsrook.qbits.slack.delivery.SlackDispatcher;
|
import com.kingsrook.qbits.slack.delivery.SlackDispatcher;
|
||||||
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
||||||
import com.kingsrook.qbits.slack.oauth.SlackConnect;
|
import com.kingsrook.qbits.slack.oauth.SlackConnect;
|
||||||
import com.kingsrook.qbits.slack.oauth.SlackOAuthState;
|
|
||||||
import com.kingsrook.qbits.slack.readiness.SlackReadiness;
|
import com.kingsrook.qbits.slack.readiness.SlackReadiness;
|
||||||
import com.kingsrook.qbits.slack.template.SlackTemplates;
|
import com.kingsrook.qbits.slack.template.SlackTemplates;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||||
@@ -43,9 +42,6 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
|
|||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
|
||||||
@@ -71,9 +67,6 @@ public final class SlackProcesses
|
|||||||
public static final String MAINTAIN = "slack_readiness";
|
public static final String MAINTAIN = "slack_readiness";
|
||||||
public static final String DISPATCH = "slack_dispatch";
|
public static final String DISPATCH = "slack_dispatch";
|
||||||
|
|
||||||
/** The one screen of Connect Slack and Install: its HTML is the process value "continue.html". */
|
|
||||||
public static final String CONTINUE_STEP = "continue";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@@ -117,63 +110,31 @@ public final class SlackProcesses
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** OAuth starts without requiring an existing connection or selected record.
|
** OAuth starts without requiring an existing connection or selected record.
|
||||||
** The one screen is an HTML component: a frontend step shows only the
|
|
||||||
** components it declares, so a view field alone renders an empty screen.
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static QProcessMetaData connectAction(String name, String label)
|
private static QProcessMetaData connectAction(String name, String label)
|
||||||
{
|
{
|
||||||
return (new QProcessMetaData().withName(name).withLabel(label).withTableName(SlackTables.CONNECTION)
|
return (new QProcessMetaData().withName(name).withLabel(label).withTableName(SlackTables.CONNECTION)
|
||||||
.withMinInputRecords(0).withPermissionRules(SlackTables.permissionRules())
|
.withMinInputRecords(0).withPermissionRules(SlackTables.permissionRules())
|
||||||
.withStep(new QBackendStepMetaData().withName("authorize").withCode(new QCodeReference(ConnectStep.class)))
|
.withStep(new QBackendStepMetaData().withName("authorize").withCode(new QCodeReference(ConnectStep.class)))
|
||||||
.withStep(new QFrontendStepMetaData().withName(CONTINUE_STEP).withLabel("Continue to Slack")
|
.withStep(new com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData().withName("result").withLabel("Connect Slack")
|
||||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.HTML))));
|
.withViewField(new com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData("authorizeUrl", com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType.STRING).withLabel("Open this Slack authorization URL"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Put the way to Slack on the process screen through the standard UI API.
|
** Return the authorize URL through the standard process UI API.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static class ConnectStep implements BackendStep
|
public static class ConnectStep implements BackendStep
|
||||||
{
|
{
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** No remote mutation happens until the user approves at Slack. The screen
|
** No remote mutation happens until the user approves at Slack.
|
||||||
** gets a link, not an address to copy; the address itself stays available
|
|
||||||
** to API clients in the authorizeUrl value.
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
public void run(RunBackendStepInput input, RunBackendStepOutput output)
|
||||||
{
|
{
|
||||||
boolean install = INSTALL.equals(input.getProcessName());
|
output.addValue("authorizeUrl", com.kingsrook.qbits.slack.preferences.SlackPreferences.connect(INSTALL.equals(input.getProcessName())));
|
||||||
String url = com.kingsrook.qbits.slack.preferences.SlackPreferences.connect(install);
|
|
||||||
output.addValue("authorizeUrl", url);
|
|
||||||
output.addValue(CONTINUE_STEP + ".html", continueHtml(url, install));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** What the screen says: one sentence about what happens next, the link to
|
|
||||||
** Slack's consent page (same tab, so Slack can return the browser here), and
|
|
||||||
** when the link stops working. The link carries its own style because a host
|
|
||||||
** UI may render links in process HTML like the text around them.
|
|
||||||
*******************************************************************************/
|
|
||||||
static String continueHtml(String authorizeUrl, boolean install)
|
|
||||||
{
|
|
||||||
String paragraph = "<p style=\"margin:0 0 12px\">";
|
|
||||||
String what = install ? "Slack will ask you to approve installing this application for your organization and connecting your own Slack account."
|
|
||||||
: "Slack will ask you to approve connecting your Slack account to this application.";
|
|
||||||
return (paragraph + what + " Afterwards you return to your Slack preferences, which show the result.</p>"
|
|
||||||
+ paragraph + "<a href=\"" + escapeHtml(authorizeUrl) + "\" style=\"display:inline-block;padding:8px 16px;border-radius:6px;"
|
|
||||||
+ "background:#4A154B;color:#ffffff;font-weight:600;text-decoration:none\">Continue to Slack</a></p>"
|
|
||||||
+ "<p style=\"margin:0\">This link expires in " + SlackOAuthState.LIFETIME_SECONDS / 60 + " minutes. If it has expired, run this action again.</p>");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Escape text for an HTML attribute or element.
|
|
||||||
*******************************************************************************/
|
|
||||||
private static String escapeHtml(String text)
|
|
||||||
{
|
|
||||||
return (text.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """).replace("'", "'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** The scheduler's entry point.
|
** The scheduler's entry point.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
/*
|
|
||||||
* QQQ - Low-code Application Framework for Engineers.
|
|
||||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
|
||||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
|
||||||
* contact@kingsrook.com
|
|
||||||
* https://github.com/Kingsrook/
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* https://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 com.kingsrook.qbits.slack;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import com.kingsrook.qbits.slack.oauth.SlackConnect;
|
|
||||||
import com.kingsrook.qbits.slack.oauth.SlackOAuthRouteProvider;
|
|
||||||
import com.kingsrook.qbits.slack.oauth.SlackOAuthState;
|
|
||||||
import com.kingsrook.qbits.slack.permissions.SlackPermissions;
|
|
||||||
import com.kingsrook.qbits.slack.processes.SlackProcesses;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.preferences.UserPreferencesAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** What a person sees when they start a Slack connection: a link to follow on
|
|
||||||
** the process screen, an address-only answer the Preferences panel can send
|
|
||||||
** the browser to, a landing page that exists, and a plain refusal when Slack
|
|
||||||
** is not set up in the environment.
|
|
||||||
*******************************************************************************/
|
|
||||||
class SlackConnectScreenTest
|
|
||||||
{
|
|
||||||
private static final String CLIENT_ID_CANARY = "client-id-canary-value";
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** A configured environment: a public URL and every Slack app secret.
|
|
||||||
*******************************************************************************/
|
|
||||||
private static SlackTestEnv configured() throws Exception
|
|
||||||
{
|
|
||||||
return (SlackTestEnv.create(false, c -> c.withPublicBaseUrl("https://example.test")
|
|
||||||
.withSecretResolver(name -> "SLACK_TOKEN_KEY".equals(name) ? SlackTokenCipherTest.KEY_ONE : "fake-application-secret")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Connect and Install each end on a screen with content. A frontend step
|
|
||||||
** shows only its declared components, so a step without one is an empty
|
|
||||||
** screen with a Return button.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void connectAndInstallScreensDeclareWhatTheyShow() throws Exception
|
|
||||||
{
|
|
||||||
try(SlackTestEnv env = configured())
|
|
||||||
{
|
|
||||||
for(String name : List.of(SlackProcesses.CONNECT, SlackProcesses.INSTALL))
|
|
||||||
{
|
|
||||||
List<QFrontendStepMetaData> screens = env.instance().getProcess(name).getStepList().stream()
|
|
||||||
.filter(QFrontendStepMetaData.class::isInstance).map(QFrontendStepMetaData.class::cast).toList();
|
|
||||||
assertThat(screens).as(name).hasSize(1);
|
|
||||||
assertThat(screens.getFirst().getName()).isEqualTo(SlackProcesses.CONTINUE_STEP);
|
|
||||||
assertThat(screens.getFirst().getLabel()).isEqualTo("Continue to Slack");
|
|
||||||
assertThat(screens.getFirst().getComponents()).as(name + " screen components").isNotEmpty()
|
|
||||||
.extracting(QFrontendComponentMetaData::getType).containsExactly(QComponentType.HTML);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** The screen's HTML is a sentence, a link named Continue to Slack whose
|
|
||||||
** target is the authorize address, and the lifetime of the link read from
|
|
||||||
** the state. The address is in the link target only, never text to copy.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void theScreenLinksToSlackAndSaysWhenTheLinkExpires() throws Exception
|
|
||||||
{
|
|
||||||
try(SlackTestEnv env = configured())
|
|
||||||
{
|
|
||||||
env.asOwner("alice").withPermissions(SlackPermissions.CONNECT, SlackPermissions.INSTALL);
|
|
||||||
RunBackendStepOutput connect = new RunBackendStepOutput();
|
|
||||||
new SlackProcesses.ConnectStep().run(new RunBackendStepInput().withProcessName(SlackProcesses.CONNECT), connect);
|
|
||||||
String url = connect.getValueString("authorizeUrl");
|
|
||||||
String html = connect.getValueString(SlackProcesses.CONTINUE_STEP + ".html");
|
|
||||||
assertThat(url).startsWith("https://slack.com/oauth/v2/authorize?").contains("&state=");
|
|
||||||
assertThat(html).contains("<a href=\"" + url.replace("&", "&") + "\"").contains(">Continue to Slack</a>");
|
|
||||||
assertThat(html).doesNotContain("target=");
|
|
||||||
assertThat(html).contains("approve connecting your Slack account");
|
|
||||||
assertThat(SlackOAuthState.LIFETIME_SECONDS).isEqualTo(600);
|
|
||||||
assertThat(html).contains("This link expires in 10 minutes.");
|
|
||||||
assertThat(html.replaceAll("<[^>]*>", " ")).as("visible text").doesNotContain("https://", "slack.com", "state=");
|
|
||||||
|
|
||||||
RunBackendStepOutput install = new RunBackendStepOutput();
|
|
||||||
new SlackProcesses.ConnectStep().run(new RunBackendStepInput().withProcessName(SlackProcesses.INSTALL), install);
|
|
||||||
assertThat(install.getValueString(SlackProcesses.CONTINUE_STEP + ".html"))
|
|
||||||
.contains("installing this application for your organization", ">Continue to Slack</a>", "scope=chat");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Run as the UI runs it: the process stops on its one screen and the
|
|
||||||
** screen's HTML is among the values the UI API returns for it.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void installRunStopsOnTheContinueScreenWithItsHtml() throws Exception
|
|
||||||
{
|
|
||||||
try(SlackTestEnv env = configured())
|
|
||||||
{
|
|
||||||
env.asOwner("alice").setPermissions(Set.of(SlackPermissions.CONNECT, SlackPermissions.INSTALL));
|
|
||||||
RunProcessInput input = new RunProcessInput();
|
|
||||||
input.setProcessName(SlackProcesses.INSTALL);
|
|
||||||
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
|
|
||||||
RunProcessOutput output = new RunProcessAction().execute(input);
|
|
||||||
assertThat(output.getException()).isEmpty();
|
|
||||||
assertThat(output.getProcessState().getNextStepName()).hasValue(SlackProcesses.CONTINUE_STEP);
|
|
||||||
assertThat(String.valueOf(output.getValues().get(SlackProcesses.CONTINUE_STEP + ".html")))
|
|
||||||
.contains("<a href=\"https://slack.com/oauth/v2/authorize?", ">Continue to Slack</a>", "This link expires in 10 minutes.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** The Preferences action answers with the address and nothing else: the UI
|
|
||||||
** follows a message that is only an https address, and shows any other.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void thePreferencesActionAnswersWithTheAddressAlone() throws Exception
|
|
||||||
{
|
|
||||||
try(SlackTestEnv env = configured())
|
|
||||||
{
|
|
||||||
env.asOwner("alice").setPermissions(Set.of(SlackPermissions.CONNECT, SlackPermissions.INSTALL));
|
|
||||||
for(String name : List.of(SlackProcesses.CONNECT, SlackProcesses.INSTALL))
|
|
||||||
{
|
|
||||||
String message = new UserPreferencesAction().runAction("slack", name, Map.of()).getMessage();
|
|
||||||
assertThat(message).as(name).startsWith("https://slack.com/oauth/v2/authorize?").doesNotContainAnyWhitespaces();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** After Slack, the browser lands on the Slack section of the host's
|
|
||||||
** Preferences page, which is under /app, with a fixed outcome code.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void theCallbackReturnsToTheSlackPreferencesPage()
|
|
||||||
{
|
|
||||||
assertThat(SlackOAuthRouteProvider.RETURN_PATH).isEqualTo("/app/preferences?section=slack");
|
|
||||||
assertThat(SlackOAuthRouteProvider.returnUrl("https://example.test/", null)).isEqualTo("https://example.test/app/preferences?section=slack&result=connected");
|
|
||||||
assertThat(SlackOAuthRouteProvider.returnUrl("https://example.test", "authorization_denied"))
|
|
||||||
.isEqualTo("https://example.test/app/preferences?section=slack&error=authorization_denied");
|
|
||||||
assertThat(SlackOAuthRouteProvider.returnUrl(null, "connect_failed")).isEqualTo("/app/preferences?section=slack&error=connect_failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** An environment that lacks the public URL, the Slack app secrets or both
|
|
||||||
** says so, says which, and names the missing variables, never a value.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void anEnvironmentWithoutSlackSaysWhatIsMissing() throws Exception
|
|
||||||
{
|
|
||||||
Function<String, String> noSecrets = name -> null;
|
|
||||||
Function<String, String> onlyClientId = name -> "SLACK_CLIENT_ID".equals(name) ? CLIENT_ID_CANARY : null;
|
|
||||||
Function<String, String> allSecrets = name -> "SLACK_TOKEN_KEY".equals(name) ? SlackTokenCipherTest.KEY_ONE : "fake-application-secret";
|
|
||||||
|
|
||||||
assertThat(refusal(null, allSecrets)).isEqualTo("Slack is not set up in this environment: the public URL is not configured.");
|
|
||||||
assertThat(refusal("https://example.test", noSecrets)).isEqualTo("Slack is not set up in this environment: "
|
|
||||||
+ "the Slack app secrets are not configured (missing SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_TOKEN_KEY).");
|
|
||||||
assertThat(refusal(null, onlyClientId)).isEqualTo("Slack is not set up in this environment: "
|
|
||||||
+ "the public URL and the Slack app secrets are not configured (missing SLACK_CLIENT_SECRET, SLACK_TOKEN_KEY).")
|
|
||||||
.doesNotContain(CLIENT_ID_CANARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Start a connection every way a person can, in an environment built with
|
|
||||||
** the given public URL and secrets, and return the one refusal they all give.
|
|
||||||
** A user-facing exception is what the UI API shows as written.
|
|
||||||
*******************************************************************************/
|
|
||||||
private static String refusal(String publicBaseUrl, Function<String, String> secrets) throws Exception
|
|
||||||
{
|
|
||||||
try(SlackTestEnv env = SlackTestEnv.create(false, c -> c.withPublicBaseUrl(publicBaseUrl).withSecretResolver(secrets)))
|
|
||||||
{
|
|
||||||
env.asOwner("alice").setPermissions(Set.of(SlackPermissions.CONNECT));
|
|
||||||
String expected = SlackConnect.notSetUp(env.config());
|
|
||||||
assertThat(expected).isNotNull();
|
|
||||||
assertThatThrownBy(() -> SlackConnect.startUrl("alice", "user")).isInstanceOf(QUserFacingException.class).hasMessage(expected);
|
|
||||||
assertThatThrownBy(() -> new UserPreferencesAction().runAction("slack", SlackProcesses.CONNECT, Map.of()))
|
|
||||||
.isInstanceOf(QUserFacingException.class).hasMessage(expected);
|
|
||||||
assertThatThrownBy(() -> new SlackProcesses.ConnectStep().run(new RunBackendStepInput().withProcessName(SlackProcesses.CONNECT), new RunBackendStepOutput()))
|
|
||||||
.isInstanceOf(QUserFacingException.class).hasMessage(expected);
|
|
||||||
return (expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** A configured environment has nothing to refuse.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void aConfiguredEnvironmentIsNotRefused() throws Exception
|
|
||||||
{
|
|
||||||
try(SlackTestEnv env = configured())
|
|
||||||
{
|
|
||||||
assertThat(SlackConnect.notSetUp(env.config())).isNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,18 +25,16 @@ import java.util.Map;
|
|||||||
import com.kingsrook.qbits.slack.api.SlackApi;
|
import com.kingsrook.qbits.slack.api.SlackApi;
|
||||||
import com.kingsrook.qbits.slack.delivery.SlackDeliveryDestination;
|
import com.kingsrook.qbits.slack.delivery.SlackDeliveryDestination;
|
||||||
import com.kingsrook.qbits.slack.delivery.SlackDispatcher;
|
import com.kingsrook.qbits.slack.delivery.SlackDispatcher;
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryAttachment; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryDestinationContext; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryRequest; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
|
import com.kingsrook.qbits.slack.delivery.standin.DeliveryResult; // STAND-IN(core=QRun-IO/qqq#1205, removeBy=4.1.0-RC.8)
|
||||||
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
import com.kingsrook.qbits.slack.metadata.SlackTables;
|
||||||
import com.kingsrook.qbits.slack.security.SlackSecurity;
|
import com.kingsrook.qbits.slack.security.SlackSecurity;
|
||||||
import com.kingsrook.qbits.slack.support.SlackStore;
|
import com.kingsrook.qbits.slack.support.SlackStore;
|
||||||
import com.kingsrook.qbits.slack.template.SlackTemplates;
|
import com.kingsrook.qbits.slack.template.SlackTemplates;
|
||||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryAttachment;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryDestinationContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryDestinations;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryDestinationsMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryRequest;
|
|
||||||
import com.kingsrook.qqq.backend.core.delivery.DeliveryResult;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
@@ -128,25 +126,6 @@ class SlackDeliveryDestinationTest
|
|||||||
assertThat(context.getDestination().getMaxAttachmentBytes()).isEqualTo(env.config().getMaxAttachmentBytes());
|
assertThat(context.getDestination().getMaxAttachmentBytes()).isEqualTo(env.config().getMaxAttachmentBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** The producer registers Slack once in the core delivery registry, and core
|
|
||||||
** loads this implementation from it.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void registeredWithCoreDelivery() throws Exception
|
|
||||||
{
|
|
||||||
var registry = DeliveryDestinationsMetaData.of(env.instance());
|
|
||||||
assertThat(registry).isNotNull();
|
|
||||||
SlackDeliveryDestination.register(env.instance(), env.config());
|
|
||||||
var registered = registry.getDestinations().stream().filter(d -> SlackDeliveryDestination.NAME.equals(d.getName())).toList();
|
|
||||||
assertThat(registered).singleElement().satisfies(d ->
|
|
||||||
{
|
|
||||||
assertThat(d.getPermissionName()).isEqualTo("slack.send");
|
|
||||||
assertThat(d.getMaxAttachmentBytes()).isEqualTo(env.config().getMaxAttachmentBytes());
|
|
||||||
});
|
|
||||||
assertThat(DeliveryDestinations.implementation(registered.getFirst())).isInstanceOf(SlackDeliveryDestination.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** DM and channel use the delivery template and preserve the idempotency key.
|
** DM and channel use the delivery template and preserve the idempotency key.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
@@ -98,12 +98,12 @@ class SlackOAuthRouteTest
|
|||||||
URI uri = URI.create("http://127.0.0.1:" + app.port() + env.config().getOauthCallbackPath() + "?code=code&state=" + URLEncoder.encode(signed, StandardCharsets.UTF_8));
|
URI uri = URI.create("http://127.0.0.1:" + app.port() + env.config().getOauthCallbackPath() + "?code=code&state=" + URLEncoder.encode(signed, StandardCharsets.UTF_8));
|
||||||
var unsigned = client.send(HttpRequest.newBuilder(uri).GET().build(), HttpResponse.BodyHandlers.ofString());
|
var unsigned = client.send(HttpRequest.newBuilder(uri).GET().build(), HttpResponse.BodyHandlers.ofString());
|
||||||
assertThat(unsigned.statusCode()).isEqualTo(302);
|
assertThat(unsigned.statusCode()).isEqualTo(302);
|
||||||
assertThat(unsigned.headers().firstValue("location").orElseThrow()).isEqualTo("https://example.test/app/preferences?section=slack&error=sign_in_required");
|
assertThat(unsigned.headers().firstValue("location").orElseThrow()).endsWith("&error=sign_in_required");
|
||||||
env.slack().script("oauth.v2.access", FakeSlackServer.Reply.of(200,
|
env.slack().script("oauth.v2.access", FakeSlackServer.Reply.of(200,
|
||||||
"{\"ok\":true,\"team\":{\"id\":\"T1\"},\"authed_user\":{\"id\":\"UA\",\"access_token\":\"xoxp-route-token\"}}"));
|
"{\"ok\":true,\"team\":{\"id\":\"T1\"},\"authed_user\":{\"id\":\"UA\",\"access_token\":\"xoxp-route-token\"}}"));
|
||||||
var request = HttpRequest.newBuilder(uri).header("Cookie", "sessionUUID=" + session.getUuid()).GET().build();
|
var request = HttpRequest.newBuilder(uri).header("Cookie", "sessionUUID=" + session.getUuid()).GET().build();
|
||||||
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
assertThat(response.headers().firstValue("location").orElseThrow()).isEqualTo("https://example.test/app/preferences?section=slack&result=connected");
|
assertThat(response.headers().firstValue("location").orElseThrow()).isEqualTo("https://example.test/preferences?section=slack&result=connected");
|
||||||
assertThat(response.headers().map().toString()).doesNotContain("xoxp-route-token", "fake-secret");
|
assertThat(response.headers().map().toString()).doesNotContain("xoxp-route-token", "fake-secret");
|
||||||
assertThat(client.send(request, HttpResponse.BodyHandlers.ofString()).headers().firstValue("location").orElseThrow()).endsWith("&error=connect_failed");
|
assertThat(client.send(request, HttpResponse.BodyHandlers.ofString()).headers().firstValue("location").orElseThrow()).endsWith("&error=connect_failed");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user