mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Add interactive check and rework api [stage]
This commit is contained in:
parent
c5608bd23c
commit
adb621dab4
16 changed files with 304 additions and 60 deletions
|
@ -3,6 +3,8 @@ package io.xpipe.app.beacon.impl;
|
|||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.util.AskpassAlert;
|
||||
import io.xpipe.app.util.SecretManager;
|
||||
import io.xpipe.app.util.SecretQueryState;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.AskpassExchange;
|
||||
|
||||
public class AskpassExchangeImpl extends AskpassExchange {
|
||||
|
@ -13,7 +15,7 @@ public class AskpassExchangeImpl extends AskpassExchange {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
|
||||
if (msg.getRequest() == null) {
|
||||
var r = AskpassAlert.queryRaw(msg.getPrompt(), null);
|
||||
return Response.builder().value(r.getSecret()).build();
|
||||
|
@ -23,13 +25,16 @@ public class AskpassExchangeImpl extends AskpassExchange {
|
|||
? SecretManager.getProgress(msg.getRequest(), msg.getSecretId())
|
||||
: SecretManager.getProgress(msg.getRequest());
|
||||
if (found.isEmpty()) {
|
||||
return Response.builder().build();
|
||||
throw new BeaconClientException("No password was provided");
|
||||
}
|
||||
|
||||
var p = found.get();
|
||||
var secret = p.process(msg.getPrompt());
|
||||
if (p.getState() != SecretQueryState.NORMAL) {
|
||||
throw new BeaconClientException(SecretQueryState.toErrorMessage(p.getState()));
|
||||
}
|
||||
return Response.builder()
|
||||
.value(secret != null ? secret.inPlace() : null)
|
||||
.value(secret.inPlace())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public class FsScriptExchangeImpl extends FsScriptExchange {
|
|||
var shell = AppBeaconServer.get().getCache().getShellSession(msg.getConnection());
|
||||
var data = new String(AppBeaconServer.get().getCache().getBlob(msg.getBlob()), StandardCharsets.UTF_8);
|
||||
var file = ScriptHelper.getExecScriptFile(shell.getControl());
|
||||
shell.getControl().getShellDialect().createScriptTextFileWriteCommand(shell.getControl(), data, file.toString());
|
||||
shell.getControl().getShellDialect().createScriptTextFileWriteCommand(shell.getControl(), data, file.toString()).execute();
|
||||
return Response.builder().path(file).build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
|
|||
var existing = AppBeaconServer.get().getCache().getShellSessions().stream()
|
||||
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
||||
.findFirst();
|
||||
if (existing.isPresent()) {
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
||||
var control = s.control().start();
|
||||
var control = (existing.isPresent() ? existing.get().getControl() : s.control());
|
||||
control.setNonInteractive();
|
||||
control.start();
|
||||
if (existing.isEmpty()) {
|
||||
AppBeaconServer.get().getCache().getShellSessions().add(new BeaconShellSession(e, control));
|
||||
return Response.builder().build();
|
||||
}
|
||||
return Response.builder().shellDialect(control.getShellDialect()).osType(control.getOsType()).osName(control.getOsName()).temp(control.getSystemTemporaryDirectory()).build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public class AskpassAlert {
|
|||
|
||||
public static SecretQueryResult queryRaw(String prompt, InPlaceSecretValue secretValue) {
|
||||
if (!PlatformState.initPlatformIfNeeded()) {
|
||||
return new SecretQueryResult(null, true);
|
||||
return new SecretQueryResult(null, SecretQueryState.CANCELLED);
|
||||
}
|
||||
|
||||
AppStyle.init();
|
||||
|
@ -103,6 +103,6 @@ public class AskpassAlert {
|
|||
return prop.getValue() != null ? prop.getValue() : InPlaceSecretValue.of("");
|
||||
})
|
||||
.orElse(null);
|
||||
return new SecretQueryResult(r, r == null);
|
||||
return new SecretQueryResult(r, r == null ? SecretQueryState.CANCELLED : SecretQueryState.NORMAL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,8 +34,9 @@ public class SecretManager {
|
|||
List<SecretQuery> suppliers,
|
||||
SecretQuery fallback,
|
||||
List<SecretQueryFilter> filters,
|
||||
CountDown countDown) {
|
||||
var p = new SecretQueryProgress(request, storeId, suppliers, fallback, filters, countDown);
|
||||
CountDown countDown,
|
||||
boolean interactive) {
|
||||
var p = new SecretQueryProgress(request, storeId, suppliers, fallback, filters, countDown, interactive);
|
||||
progress.add(p);
|
||||
return p;
|
||||
}
|
||||
|
@ -55,14 +56,14 @@ public class SecretManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static SecretValue retrieve(SecretRetrievalStrategy strategy, String prompt, UUID secretId, int sub) {
|
||||
public static SecretValue retrieve(SecretRetrievalStrategy strategy, String prompt, UUID secretId, int sub, boolean interactive) {
|
||||
if (!strategy.expectsQuery()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var uuid = UUID.randomUUID();
|
||||
var p = expectAskpass(
|
||||
uuid, secretId, List.of(strategy.query()), SecretQuery.prompt(false), List.of(), CountDown.of());
|
||||
uuid, secretId, List.of(strategy.query()), SecretQuery.prompt(false), List.of(), CountDown.of(), interactive);
|
||||
p.preAdvance(sub);
|
||||
var r = p.process(prompt);
|
||||
completeRequest(uuid);
|
||||
|
|
|
@ -32,13 +32,13 @@ public interface SecretQuery {
|
|||
|
||||
var inPlace = found.get().getSecret().inPlace();
|
||||
var r = AskpassAlert.queryRaw(prompt, inPlace);
|
||||
return r.isCancelled() ? Optional.of(r) : found;
|
||||
return r.getState() != SecretQueryState.NORMAL ? Optional.of(r) : found;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretQueryResult query(String prompt) {
|
||||
var r = original.query(prompt);
|
||||
if (r.isCancelled()) {
|
||||
if (r.getState() != SecretQueryState.NORMAL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ public interface SecretQuery {
|
|||
|
||||
default Optional<SecretQueryResult> retrieveCache(String prompt, SecretReference reference) {
|
||||
var r = SecretManager.get(reference);
|
||||
return r.map(secretValue -> new SecretQueryResult(secretValue, false));
|
||||
return r.map(secretValue -> new SecretQueryResult(secretValue, SecretQueryState.NORMAL));
|
||||
}
|
||||
|
||||
SecretQueryResult query(String prompt);
|
||||
|
|
|
@ -22,7 +22,8 @@ public class SecretQueryProgress {
|
|||
private final List<SecretQueryFilter> filters;
|
||||
private final List<String> seenPrompts;
|
||||
private final CountDown countDown;
|
||||
private boolean requestCancelled;
|
||||
private final boolean interactive;
|
||||
private SecretQueryState state = SecretQueryState.NORMAL;
|
||||
|
||||
public SecretQueryProgress(
|
||||
@NonNull UUID requestId,
|
||||
|
@ -30,13 +31,16 @@ public class SecretQueryProgress {
|
|||
@NonNull List<SecretQuery> suppliers,
|
||||
@NonNull SecretQuery fallback,
|
||||
@NonNull List<SecretQueryFilter> filters,
|
||||
@NonNull CountDown countDown) {
|
||||
@NonNull CountDown countDown,
|
||||
boolean interactive
|
||||
) {
|
||||
this.requestId = requestId;
|
||||
this.storeId = storeId;
|
||||
this.suppliers = new ArrayList<>(suppliers);
|
||||
this.fallback = fallback;
|
||||
this.filters = filters;
|
||||
this.countDown = countDown;
|
||||
this.interactive = interactive;
|
||||
this.seenPrompts = new ArrayList<>();
|
||||
}
|
||||
|
||||
|
@ -49,7 +53,7 @@ public class SecretQueryProgress {
|
|||
|
||||
public SecretValue process(String prompt) {
|
||||
// Cancel early
|
||||
if (requestCancelled) {
|
||||
if (state != SecretQueryState.NORMAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -67,11 +71,18 @@ public class SecretQueryProgress {
|
|||
|
||||
var firstSeenIndex = seenPrompts.indexOf(prompt);
|
||||
if (firstSeenIndex >= suppliers.size()) {
|
||||
// Check whether we can have user inputs
|
||||
if (!interactive && fallback.requiresUserInteraction()) {
|
||||
state = SecretQueryState.NON_INTERACTIVE;
|
||||
return null;
|
||||
}
|
||||
|
||||
countDown.pause();
|
||||
var r = fallback.query(prompt);
|
||||
countDown.resume();
|
||||
if (r.isCancelled()) {
|
||||
requestCancelled = true;
|
||||
|
||||
if (r.getState() != SecretQueryState.NORMAL) {
|
||||
state = r.getState();
|
||||
return null;
|
||||
}
|
||||
return r.getSecret();
|
||||
|
@ -82,6 +93,12 @@ public class SecretQueryProgress {
|
|||
var shouldCache = shouldCache(sup, prompt);
|
||||
var wasLastPrompt = firstSeenIndex == seenPrompts.size() - 1;
|
||||
|
||||
// Check whether we can have user inputs
|
||||
if (!interactive && sup.requiresUserInteraction()) {
|
||||
state = SecretQueryState.NON_INTERACTIVE;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clear cache if secret was wrong/queried again
|
||||
// Check whether this is actually the last prompt seen as it might happen that
|
||||
// previous prompts get rolled back again when one further down is wrong
|
||||
|
@ -91,7 +108,7 @@ public class SecretQueryProgress {
|
|||
|
||||
// If we supplied a wrong secret and cannot retry, cancel the entire request
|
||||
if (seenBefore && wasLastPrompt && !sup.retryOnFail()) {
|
||||
requestCancelled = true;
|
||||
state = SecretQueryState.FIXED_SECRET_WRONG;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -100,8 +117,8 @@ public class SecretQueryProgress {
|
|||
var cached = sup.retrieveCache(prompt, ref);
|
||||
countDown.resume();
|
||||
if (cached.isPresent()) {
|
||||
if (cached.get().isCancelled()) {
|
||||
requestCancelled = true;
|
||||
if (cached.get().getState() != SecretQueryState.NORMAL) {
|
||||
state = cached.get().getState();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -113,8 +130,8 @@ public class SecretQueryProgress {
|
|||
var r = sup.query(prompt);
|
||||
countDown.resume();
|
||||
|
||||
if (r.isCancelled()) {
|
||||
requestCancelled = true;
|
||||
if (r.getState() != SecretQueryState.NORMAL) {
|
||||
state = r.getState();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,5 +8,5 @@ import lombok.Value;
|
|||
public class SecretQueryResult {
|
||||
|
||||
SecretValue secret;
|
||||
boolean cancelled;
|
||||
SecretQueryState state;
|
||||
}
|
||||
|
|
33
app/src/main/java/io/xpipe/app/util/SecretQueryState.java
Normal file
33
app/src/main/java/io/xpipe/app/util/SecretQueryState.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
public enum SecretQueryState {
|
||||
NORMAL,
|
||||
CANCELLED,
|
||||
NON_INTERACTIVE,
|
||||
FIXED_SECRET_WRONG,
|
||||
RETRIEVAL_FAILURE;
|
||||
|
||||
public static String toErrorMessage(SecretQueryState s) {
|
||||
if (s == null) {
|
||||
return "None";
|
||||
}
|
||||
|
||||
return switch (s) {
|
||||
case NORMAL -> {
|
||||
yield "None";
|
||||
}
|
||||
case CANCELLED -> {
|
||||
yield "Operation was cancelled";
|
||||
}
|
||||
case NON_INTERACTIVE -> {
|
||||
yield "Session is not interactive but required user input";
|
||||
}
|
||||
case FIXED_SECRET_WRONG -> {
|
||||
yield "Provided secret is wrong";
|
||||
}
|
||||
case RETRIEVAL_FAILURE -> {
|
||||
yield "Failed to retrieve secret";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -60,7 +60,7 @@ public interface SecretRetrievalStrategy {
|
|||
@Override
|
||||
public SecretQueryResult query(String prompt) {
|
||||
return new SecretQueryResult(
|
||||
value != null ? value.getInternalSecret() : InPlaceSecretValue.of(""), false);
|
||||
value != null ? value.getInternalSecret() : InPlaceSecretValue.of(""), SecretQueryState.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -125,7 +125,7 @@ public interface SecretRetrievalStrategy {
|
|||
public SecretQueryResult query(String prompt) {
|
||||
var cmd = AppPrefs.get().passwordManagerString(key);
|
||||
if (cmd == null) {
|
||||
return new SecretQueryResult(null, true);
|
||||
return new SecretQueryResult(null, SecretQueryState.RETRIEVAL_FAILURE);
|
||||
}
|
||||
|
||||
String r;
|
||||
|
@ -134,7 +134,7 @@ public interface SecretRetrievalStrategy {
|
|||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex)
|
||||
.handle();
|
||||
return new SecretQueryResult(null, true);
|
||||
return new SecretQueryResult(null, SecretQueryState.RETRIEVAL_FAILURE);
|
||||
}
|
||||
|
||||
if (r.lines().count() > 1 || r.isBlank()) {
|
||||
|
@ -145,7 +145,7 @@ public interface SecretRetrievalStrategy {
|
|||
+ " you will have to change the command and/or password key."));
|
||||
}
|
||||
|
||||
return new SecretQueryResult(InPlaceSecretValue.of(r), false);
|
||||
return new SecretQueryResult(InPlaceSecretValue.of(r), SecretQueryState.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -180,11 +180,11 @@ public interface SecretRetrievalStrategy {
|
|||
@Override
|
||||
public SecretQueryResult query(String prompt) {
|
||||
try (var cc = new LocalStore().control().command(command).start()) {
|
||||
return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), false);
|
||||
return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), SecretQueryState.NORMAL);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable("Unable to retrieve password with command " + command, ex)
|
||||
.handle();
|
||||
return new SecretQueryResult(null, true);
|
||||
return new SecretQueryResult(null, SecretQueryState.RETRIEVAL_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ You can get started by either using this page as an API reference or alternative
|
|||
<a download href="/openapi.yaml" style="font-size: 20px">OpenAPI .yaml specification</a>
|
||||
|
||||
The XPipe application will start up an HTTP server that can be used to send requests.
|
||||
You can change the port of it in the settings menu.
|
||||
Note that this server is HTTP-only for now as it runs only on localhost. HTTPS requests are not accepted.
|
||||
|
||||
This allows you to programmatically manage remote systems.
|
||||
|
@ -104,8 +103,8 @@ Note that for development you can also turn off the required authentication in t
|
|||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The handshake was successful. The returned token can be used for authentication in this session. The token is valid as long as XPipe is running.|[HandshakeResponse](#schemahandshakeresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="success">
|
||||
This operation does not require authentication
|
||||
|
@ -305,16 +304,24 @@ All matching is case insensitive.
|
|||
}
|
||||
```
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="query-connections-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The query was successful. The body contains all matched connections.|[ConnectionQueryResponse](#schemaconnectionqueryresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -463,16 +470,26 @@ These errors will be returned with the HTTP return code 500.
|
|||
|---|---|---|---|---|
|
||||
|body|body|[ShellStartRequest](#schemashellstartrequest)|true|none|
|
||||
|
||||
> Example responses
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="start-shell-connection-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The shell session was started.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -489,6 +506,7 @@ const inputBody = '{
|
|||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
'Accept':'application/json',
|
||||
'Authorization':'Bearer {access-token}'
|
||||
};
|
||||
|
||||
|
@ -510,6 +528,7 @@ fetch('http://localhost:21723/shell/start',
|
|||
import requests
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {access-token}'
|
||||
}
|
||||
|
||||
|
@ -531,6 +550,7 @@ var request = HttpRequest
|
|||
.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
|
@ -556,6 +576,7 @@ func main() {
|
|||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": []string{"application/json"},
|
||||
"Accept": []string{"application/json"},
|
||||
"Authorization": []string{"Bearer {access-token}"},
|
||||
}
|
||||
|
||||
|
@ -573,7 +594,7 @@ func main() {
|
|||
```shell
|
||||
# You can also use wget
|
||||
curl -X POST http://localhost:21723/shell/start \
|
||||
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
|
@ -609,16 +630,26 @@ If the shell is busy or stuck, you might have to work with timeouts to account f
|
|||
|---|---|---|---|---|
|
||||
|body|body|[ShellStopRequest](#schemashellstoprequest)|true|none|
|
||||
|
||||
> Example responses
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="stop-shell-connection-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The shell session was stopped.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -635,6 +666,7 @@ const inputBody = '{
|
|||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
'Accept':'application/json',
|
||||
'Authorization':'Bearer {access-token}'
|
||||
};
|
||||
|
||||
|
@ -656,6 +688,7 @@ fetch('http://localhost:21723/shell/stop',
|
|||
import requests
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {access-token}'
|
||||
}
|
||||
|
||||
|
@ -677,6 +710,7 @@ var request = HttpRequest
|
|||
.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
|
@ -702,6 +736,7 @@ func main() {
|
|||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": []string{"application/json"},
|
||||
"Accept": []string{"application/json"},
|
||||
"Authorization": []string{"Bearer {access-token}"},
|
||||
}
|
||||
|
||||
|
@ -719,7 +754,7 @@ func main() {
|
|||
```shell
|
||||
# You can also use wget
|
||||
curl -X POST http://localhost:21723/shell/stop \
|
||||
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
|
@ -777,16 +812,24 @@ However, if any other error occurs like the shell not responding or exiting unex
|
|||
}
|
||||
```
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="execute-command-in-a-shell-session-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The shell command finished.|[ShellExecResponse](#schemashellexecresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -940,16 +983,24 @@ string
|
|||
}
|
||||
```
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="store-a-raw-blob-to-be-used-later-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The data was stored.|[FsBlobResponse](#schemafsblobresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -1081,16 +1132,26 @@ Writes blob data to a file through an active shell session.
|
|||
|---|---|---|---|---|
|
||||
|body|body|[FsWriteRequest](#schemafswriterequest)|true|none|
|
||||
|
||||
> Example responses
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="write-a-blob-to-a-remote-file-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The file was written.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -1109,6 +1170,7 @@ const inputBody = '{
|
|||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
'Accept':'application/json',
|
||||
'Authorization':'Bearer {access-token}'
|
||||
};
|
||||
|
||||
|
@ -1130,6 +1192,7 @@ fetch('http://localhost:21723/fs/write',
|
|||
import requests
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {access-token}'
|
||||
}
|
||||
|
||||
|
@ -1153,6 +1216,7 @@ var request = HttpRequest
|
|||
.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
|
@ -1180,6 +1244,7 @@ func main() {
|
|||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": []string{"application/json"},
|
||||
"Accept": []string{"application/json"},
|
||||
"Authorization": []string{"Bearer {access-token}"},
|
||||
}
|
||||
|
||||
|
@ -1197,7 +1262,7 @@ func main() {
|
|||
```shell
|
||||
# You can also use wget
|
||||
curl -X POST http://localhost:21723/fs/write \
|
||||
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
|
@ -1245,16 +1310,24 @@ This can be used to run more complex commands on remote systems.
|
|||
}
|
||||
```
|
||||
|
||||
> 400 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="create-a-shell-script-file-from-a-blob-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The script file was created.|[FsScriptResponse](#schemafsscriptresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
|
@ -1783,3 +1856,58 @@ and
|
|||
|*anonymous*|object|false|none|none|
|
||||
|» name|string|true|none|The name of the client.|
|
||||
|
||||
<h2 id="tocS_ClientErrorResponse">ClientErrorResponse</h2>
|
||||
|
||||
<a id="schemaclienterrorresponse"></a>
|
||||
<a id="schema_ClientErrorResponse"></a>
|
||||
<a id="tocSclienterrorresponse"></a>
|
||||
<a id="tocsclienterrorresponse"></a>
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "string"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Error returned in case of a client exception
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|message|string|true|none|The error message|
|
||||
|
||||
<h2 id="tocS_ServerErrorResponse">ServerErrorResponse</h2>
|
||||
|
||||
<a id="schemaservererrorresponse"></a>
|
||||
<a id="schema_ServerErrorResponse"></a>
|
||||
<a id="tocSservererrorresponse"></a>
|
||||
<a id="tocsservererrorresponse"></a>
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"cause": {},
|
||||
"stackTrace": [],
|
||||
"suppressed": [],
|
||||
"localizedMessage": "string",
|
||||
"message": "string"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Error returned in case of a server exception with HTTP code 500
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|error|object|true|none|The exception information|
|
||||
|» cause|object|false|none|The exception cause|
|
||||
|» stackTrace|array|false|none|The java stack trace information|
|
||||
|» suppressed|array|false|none|Any suppressed exceptions|
|
||||
|» localizedMessage|string|false|none|Not used|
|
||||
|» message|string|true|none|The error message|
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.beacon.BeaconInterface;
|
|||
import io.xpipe.core.util.SecretValue;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
|
@ -31,6 +32,7 @@ public class AskpassExchange extends BeaconInterface<AskpassExchange.Request> {
|
|||
@Builder
|
||||
@Value
|
||||
public static class Response {
|
||||
@NonNull
|
||||
SecretValue value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package io.xpipe.beacon.api;
|
||||
|
||||
import io.xpipe.beacon.BeaconInterface;
|
||||
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellDialect;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
@ -27,5 +29,14 @@ public class ShellStartExchange extends BeaconInterface<ShellStartExchange.Reque
|
|||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {}
|
||||
public static class Response {
|
||||
@NonNull
|
||||
ShellDialect shellDialect;
|
||||
@NonNull
|
||||
OsType osType;
|
||||
@NonNull
|
||||
String osName;
|
||||
@NonNull
|
||||
FilePath temp;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ import java.util.function.Function;
|
|||
|
||||
public interface ShellControl extends ProcessControl {
|
||||
|
||||
void setNonInteractive();
|
||||
|
||||
boolean isInteractive();
|
||||
|
||||
ElevationHandler getElevationHandler();
|
||||
|
||||
void setElevationHandler(ElevationHandler ref);
|
||||
|
|
45
openapi.yaml
45
openapi.yaml
|
@ -8,7 +8,6 @@ info:
|
|||
<a download href="/openapi.yaml" style="font-size: 20px">OpenAPI .yaml specification</a>
|
||||
|
||||
The XPipe application will start up an HTTP server that can be used to send requests.
|
||||
You can change the port of it in the settings menu.
|
||||
Note that this server is HTTP-only for now as it runs only on localhost. HTTPS requests are not accepted.
|
||||
|
||||
This allows you to programmatically manage remote systems.
|
||||
|
@ -544,11 +543,51 @@ components:
|
|||
description: The name of the client.
|
||||
required:
|
||||
- name
|
||||
ClientErrorResponse:
|
||||
description: Error returned in case of a client exception
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
description: The error message
|
||||
required:
|
||||
- message
|
||||
ServerErrorResponse:
|
||||
description: Error returned in case of a server exception with HTTP code 500
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: object
|
||||
description: The exception information
|
||||
properties:
|
||||
cause:
|
||||
type: object
|
||||
description: The exception cause
|
||||
stackTrace:
|
||||
type: array
|
||||
description: The java stack trace information
|
||||
suppressed:
|
||||
type: array
|
||||
description: Any suppressed exceptions
|
||||
localizedMessage:
|
||||
type: string
|
||||
description: Not used
|
||||
message:
|
||||
type: string
|
||||
description: The error message
|
||||
required:
|
||||
- message
|
||||
required:
|
||||
- error
|
||||
responses:
|
||||
Success:
|
||||
description: The action was successfully performed.
|
||||
BadRequest:
|
||||
description: Bad request. Please check error message and your parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ClientErrorResponse'
|
||||
Unauthorized:
|
||||
description: Authorization failed. Please supply a `Bearer` token via
|
||||
the `Authorization` header.
|
||||
|
@ -559,6 +598,10 @@ components:
|
|||
description: The requested resource could not be found.
|
||||
InternalServerError:
|
||||
description: Internal error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServerErrorResponse'
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
10.0-9
|
||||
10.0-10
|
||||
|
|
Loading…
Reference in a new issue