├── .changelog-generator.json ├── .github ├── dependabot.yml └── workflows │ ├── build-dependabot.yml │ ├── build.yml │ ├── publish-manual.yml │ ├── publish.yml │ ├── release.yml │ ├── reusable-build.yml │ └── reusable-publish.yml ├── .gitignore ├── .idea └── runConfigurations │ ├── build.xml │ └── release.xml ├── LICENSE ├── README.md ├── lombok.config ├── pom.xml ├── samples ├── basic │ ├── README.md │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── fonimus │ │ │ │ └── ssh │ │ │ │ └── shell │ │ │ │ └── basic │ │ │ │ ├── BasicApplication.java │ │ │ │ ├── BasicCommands.java │ │ │ │ └── BasicController.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── fonimus │ │ └── ssh │ │ └── shell │ │ └── basic │ │ └── DemoApplicationWebTest.java ├── complete │ ├── README.md │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── fonimus │ │ │ │ └── ssh │ │ │ │ └── shell │ │ │ │ └── complete │ │ │ │ ├── CompleteApplication.java │ │ │ │ ├── CompleteCommands.java │ │ │ │ ├── CompleteConfiguration.java │ │ │ │ ├── CompleteController.java │ │ │ │ ├── CompletePromptProvider.java │ │ │ │ └── CompleteSecurity.java │ │ └── resources │ │ │ ├── .ssh │ │ │ └── authorized.keys │ │ │ ├── application.yml │ │ │ └── banner.txt │ │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── fonimus │ │ └── ssh │ │ └── shell │ │ └── complete │ │ ├── AbstractDemoApplicationTest.java │ │ ├── DemoApplicationTest.java │ │ └── DemoApplicationWebEnvTest.java └── script.txt └── starter ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── github │ │ └── fonimus │ │ └── ssh │ │ └── shell │ │ ├── ExtendedCompleterAdapter.java │ │ ├── ExtendedCompletionProposal.java │ │ ├── ExtendedInput.java │ │ ├── ExtendedInteractiveShellRunner.java │ │ ├── ExtendedShell.java │ │ ├── PromptColor.java │ │ ├── SimpleTable.java │ │ ├── SshContext.java │ │ ├── SshIO.java │ │ ├── SshShellAutoConfiguration.java │ │ ├── SshShellCommandFactory.java │ │ ├── SshShellConfiguration.java │ │ ├── SshShellHelper.java │ │ ├── SshShellProperties.java │ │ ├── SshShellRunnable.java │ │ ├── SshShellTerminalDelegate.java │ │ ├── SshShellUtils.java │ │ ├── auth │ │ ├── SshAuthentication.java │ │ ├── SshShellAuthenticationProvider.java │ │ ├── SshShellPasswordAuthenticationProvider.java │ │ ├── SshShellPublicKeyAuthenticationProvider.java │ │ └── SshShellSecurityAuthenticationProvider.java │ │ ├── commands │ │ ├── AbstractCommand.java │ │ ├── AvailabilityException.java │ │ ├── ColorAligner.java │ │ ├── CommandProperties.java │ │ ├── DatasourceCommand.java │ │ ├── HistoryCommand.java │ │ ├── JmxCommand.java │ │ ├── ManageSessionsCommand.java │ │ ├── PostProcessorsCommand.java │ │ ├── ScriptCommand.java │ │ ├── SshShellComponent.java │ │ ├── StacktraceCommand.java │ │ ├── TasksCommand.java │ │ ├── actuator │ │ │ └── ActuatorCommand.java │ │ └── system │ │ │ └── SystemCommand.java │ │ ├── interactive │ │ ├── Interactive.java │ │ ├── InteractiveInput.java │ │ ├── InteractiveInputIO.java │ │ ├── KeyBinding.java │ │ ├── KeyBindingInput.java │ │ └── StoppableInteractiveInput.java │ │ ├── listeners │ │ ├── SshShellEvent.java │ │ ├── SshShellEventType.java │ │ ├── SshShellListener.java │ │ └── SshShellListenerService.java │ │ ├── manage │ │ └── SshShellSessionManager.java │ │ ├── postprocess │ │ ├── ExtendedResultHandlerService.java │ │ ├── PostProcessor.java │ │ ├── PostProcessorException.java │ │ ├── PostProcessorObject.java │ │ └── provided │ │ │ ├── GrepPostProcessor.java │ │ │ ├── HighlightPostProcessor.java │ │ │ ├── JsonPointerPostProcessor.java │ │ │ ├── PrettyJsonPostProcessor.java │ │ │ └── SavePostProcessor.java │ │ └── providers │ │ └── ExtendedFileValueProvider.java └── resources │ └── META-INF │ └── spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports └── test ├── java └── com │ └── github │ └── fonimus │ └── ssh │ └── shell │ ├── AbstractCommandTest.java │ ├── AbstractShellHelperTest.java │ ├── AbstractTest.java │ ├── ExtendedCompleterAdapterTest.java │ ├── ExtendedInputTest.java │ ├── ExtendedShellTest.java │ ├── JavaConnect.java │ ├── SshHelperTest.java │ ├── SshShellApplicationCustomAuthenticatorTest.java │ ├── SshShellApplicationExclusionsTest.java │ ├── SshShellApplicationLazyInitTest.java │ ├── SshShellApplicationSecurityTest.java │ ├── SshShellApplicationTest.java │ ├── SshShellApplicationWebTest.java │ ├── SshShellHelperTest.java │ ├── SshShellTerminalDelegateTest.java │ ├── SshShellUtilsTest.java │ ├── auth │ ├── SshShellPublicKeyAuthenticationProviderTest.java │ └── SshShellSecurityAuthenticationProviderTest.java │ ├── commands │ ├── HistoryCommandTest.java │ ├── PostProcessorsCommandTest.java │ ├── ScriptCommandTest.java │ ├── StacktraceCommandTest.java │ └── system │ │ └── SystemCommandTest.java │ ├── conf │ ├── SshShellPasswordConfigurationTest.java │ ├── SshShellSecurityConfigurationTest.java │ ├── SshShellSessionConfigurationTest.java │ └── TaskServiceTest.java │ ├── listeners │ └── SshShellListenerServiceTest.java │ ├── postprocess │ ├── ExtendedResultHandlerServiceTest.java │ ├── GrepPostProcessorTest.java │ ├── HighlightPostProcessorTest.java │ ├── JsonPointerPostProcessorTest.java │ ├── PrettyJsonPostProcessorTest.java │ └── SavePostProcessorTest.java │ └── providers │ └── ExtendedFileValueProviderTest.java └── resources ├── .ssh ├── authorized.keys ├── pub.der └── wrong_pub.der └── script.txt /.changelog-generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## 🚀 Features", 5 | "labels": [ 6 | "feature", 7 | "enhancement" 8 | ] 9 | }, 10 | { 11 | "title": "## 🐛 Fixes", 12 | "labels": [ 13 | "fix", 14 | "bug" 15 | ] 16 | }, 17 | { 18 | "title": "## ⬆️ Dependencies", 19 | "labels": [ 20 | "dependencies" 21 | ] 22 | }, 23 | { 24 | "title": "## 🧪 Tests", 25 | "labels": [ 26 | "test" 27 | ] 28 | }, 29 | { 30 | "title": "## 📝 Documentation", 31 | "labels": [ 32 | "documentation" 33 | ] 34 | } 35 | ], 36 | "ignore_labels": [ 37 | "ignore" 38 | ], 39 | "pr_template": "- #${{NUMBER}} - ${{TITLE}}", 40 | "empty_template": "No changes" 41 | } 42 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | 5 | - package-ecosystem: github-actions 6 | directory: "/" 7 | open-pull-requests-limit: 99 8 | schedule: 9 | interval: daily 10 | time: "04:00" 11 | 12 | - package-ecosystem: maven 13 | directory: "/" 14 | open-pull-requests-limit: 99 15 | schedule: 16 | interval: daily 17 | time: "04:00" 18 | -------------------------------------------------------------------------------- /.github/workflows/build-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Build Dependabot 2 | 3 | on: 4 | pull_request_target 5 | 6 | jobs: 7 | build: 8 | name: Build 9 | if: ${{ github.actor == 'dependabot[bot]' }} 10 | uses: ./.github/workflows/reusable-build.yml 11 | with: 12 | sonar_analysis: false 13 | ref: ${{ github.event.pull_request.head.sha }} 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | paths-ignore: 8 | - '**.md' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | if: ${{ github.actor != 'dependabot[bot]' }} 15 | uses: ./.github/workflows/reusable-build.yml 16 | secrets: 17 | sonar_token: ${{ secrets.SONAR_TOKEN }} 18 | gh_token: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/publish-manual.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release Manual 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Specified version to publish' 8 | required: true 9 | 10 | jobs: 11 | publish-manual: 12 | name: Manual Publish 13 | uses: ./.github/workflows/reusable-publish.yml 14 | with: 15 | version: ${{ inputs.version }} 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | jobs: 9 | publish-auto: 10 | name: Auto Publish 11 | uses: ./.github/workflows/reusable-publish.yml 12 | with: 13 | version: ${{ github.ref }} 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | type: 7 | type: choice 8 | description: 'Type of release' 9 | required: true 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | version: 15 | description: 'Specified version to release' 16 | required: false 17 | default: '' 18 | 19 | jobs: 20 | release: 21 | 22 | name: Release 23 | 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | 28 | - name: Checkout project 29 | uses: actions/checkout@v4 30 | with: 31 | persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token 32 | - name: Set up JDK 17 33 | uses: actions/setup-java@v4.2.1 34 | with: 35 | java-version: 17 36 | distribution: temurin 37 | cache: maven 38 | - name: Import GPG key 39 | id: import_gpg 40 | uses: crazy-max/ghaction-import-gpg@v6.1.0 41 | with: 42 | gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} 43 | passphrase: ${{ secrets.GPG_PASSPHRASE }} 44 | - name: GPG user IDs 45 | run: | 46 | echo "fingerprint: ${{ steps.import_gpg.outputs.fingerprint }}" 47 | echo "keyid: ${{ steps.import_gpg.outputs.keyid }}" 48 | echo "name: ${{ steps.import_gpg.outputs.name }}" 49 | echo "email: ${{ steps.import_gpg.outputs.email }}" 50 | - name: Get maven version 51 | id: version 52 | run: | 53 | export version=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) 54 | export version=$(echo "${version%%-*}") 55 | echo "::set-output name=version::$(echo $version)" 56 | - name: Print current version 57 | run: echo "Current version = ${{ steps.version.outputs.version }}" 58 | - name: 'Compute next versions' 59 | id: next 60 | uses: "WyriHaximus/github-action-next-semvers@v1" 61 | with: 62 | version: ${{ steps.version.outputs.version }} 63 | - name: Choose next version 64 | id: choose 65 | run: | 66 | export version=$(echo ${{ github.event.inputs.version }}) 67 | if [[ $version != '' ]] ; then export next=$version; elif [[ ${{ github.event.inputs.type }} == "major" ]] ; then export next=${{ steps.next.outputs.major }}; elif [[ ${{ github.event.inputs.type }} == "minor" ]] ; then export next=${{ steps.next.outputs.minor }}; else export next=${{ steps.version.outputs.version }}; fi 68 | echo "::set-output name=next::$(echo $next)" 69 | export purenext=$(echo "${next%%-*}") 70 | echo "::set-output name=purenext::$(echo $purenext)" 71 | - name: 'Get next snapshot version' 72 | id: snapshot 73 | uses: "WyriHaximus/github-action-next-semvers@v1" 74 | with: 75 | version: ${{ steps.choose.outputs.purenext }} 76 | - name: Set release and snapshot versions 77 | id: versions 78 | run: | 79 | echo "::set-output name=snapshot::$(echo ${{ steps.snapshot.outputs.patch }}-SNAPSHOT)" 80 | echo "::set-output name=release::$(echo ${{ steps.choose.outputs.next }})" 81 | - name: Log versions 82 | run: echo "Releasing version ${{ steps.versions.outputs.release }}, new dev version will be ${{ steps.versions.outputs.snapshot }}" 83 | - name: Configure git user 84 | run: | 85 | git config user.email "actions@github.com" 86 | git config user.name "GitHub Actions" 87 | - name: Configure maven settings 88 | uses: s4u/maven-settings-action@v3.0.0 89 | with: 90 | servers: '[{ "id": "ossrh", "username": "${{ secrets.OSSRH_USERNAME }}", "password": "${{ secrets.OSSRH_PASSWORD }}" }]' 91 | - name: Release 92 | run: mvn -B -ntp release:prepare release:clean -DreleaseVersion=${{ steps.versions.outputs.release }} -DdevelopmentVersion=${{ steps.versions.outputs.snapshot }} 93 | - name: Push changes 94 | uses: ad-m/github-push-action@master 95 | with: 96 | github_token: ${{ secrets.GITHUB_TOKEN }} 97 | branch: ${{ github.ref }} 98 | tags: true 99 | force: true 100 | -------------------------------------------------------------------------------- /.github/workflows/reusable-build.yml: -------------------------------------------------------------------------------- 1 | name: -Reusable Build 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | ref: 7 | required: false 8 | type: string 9 | default: '' 10 | description: Ref to checkout 11 | sonar_analysis: 12 | required: false 13 | type: boolean 14 | default: true 15 | description: Whether to make sonar analysis (sonar_token and gh_token become mandatory) 16 | secrets: 17 | sonar_token: 18 | required: false 19 | gh_token: 20 | required: false 21 | 22 | jobs: 23 | build: 24 | name: Build and analyze 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | ref: ${{ inputs.ref }} 30 | - name: Set up JDK 17 31 | uses: actions/setup-java@v4.2.1 32 | with: 33 | java-version: 17 34 | distribution: temurin 35 | cache: maven 36 | 37 | - name: Build 38 | run: mvn -B -ntp verify 39 | 40 | - name: Cache sonar packages 41 | if: ${{ inputs.sonar_analysis == true }} 42 | uses: actions/cache@v4.0.2 43 | with: 44 | path: ~/.sonar/cache 45 | key: ${{ runner.os }}-sonar 46 | restore-keys: ${{ runner.os }}-sonar 47 | 48 | - name: Analyze 49 | if: ${{ inputs.sonar_analysis == true }} 50 | run: mvn -B -ntp org.sonarsource.scanner.maven:sonar-maven-plugin:sonar 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.gh_token }} 53 | SONAR_TOKEN: ${{ secrets.sonar_token }} 54 | -------------------------------------------------------------------------------- /.github/workflows/reusable-publish.yml: -------------------------------------------------------------------------------- 1 | name: -Reusable Publish Release 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | version: 7 | required: true 8 | type: string 9 | description: Version to publish 10 | 11 | jobs: 12 | publish: 13 | name: Publish ${{ inputs.version }} 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Is Pre Release ? 20 | id: pre_release 21 | if: github.event_name != 'workflow_dispatch' 22 | uses: heineiuo/create-changelogs@v0.2.8 23 | 24 | - name: Build Changelog 25 | id: changelog 26 | uses: mikepenz/release-changelog-builder-action@v4 27 | with: 28 | configuration: .changelog-generator.json 29 | ignorePreReleases: true 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Create Release 34 | id: create-release 35 | uses: actions/create-release@latest 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | with: 39 | tag_name: ${{ inputs.version }} 40 | release_name: Release ${{ inputs.version }} 41 | body: ${{ steps.changelog.outputs.changelog }} 42 | draft: false 43 | prerelease: ${{ github.event_name != 'workflow_dispatch' && steps.pre_release.outputs.release_type == 'prerelease' }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # project 2 | target/ 3 | 4 | # intellij 5 | .idea/* 6 | !.idea/runConfigurations 7 | *.iml 8 | 9 | # public key file example 10 | samples/public-keys-sample 11 | 12 | # mac os 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/runConfigurations/release.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.log.fieldName = LOGGER 2 | lombok.equalsAndHashCode.callSuper = call 3 | lombok.addLombokGeneratedAnnotation = true 4 | lombok.copyableAnnotations += org.springframework.context.annotation.Lazy 5 | lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Autowired 6 | -------------------------------------------------------------------------------- /samples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | 1. Build sample application 4 | 5 | ```bash 6 | mvn clean install -f samples/basic [-DskipTests] 7 | ``` 8 | 1. Start application 9 | 10 | ```bash 11 | java -jar samples/basic/target/ssh-shell-spring-boot-basic-sample[-version].jar 12 | ``` 13 | 1. Connect to application via ssh (default password: pass) 14 | 15 | ```bash 16 | ~/home$ ssh -p 2222 user@localhost 17 | Password authentication 18 | Password: [password] 19 | 20 | Please type `help` to see available commands 21 | basic::>help 22 | AVAILABLE COMMANDS 23 | 24 | Built-In Commands 25 | clear: Clear the shell screen. 26 | exit, quit: Exit the shell. 27 | help: Display help about available commands. 28 | history: Display or save the history of previously run commands 29 | postprocessors: Display the available post processors 30 | script: Read and execute commands from a file. 31 | stacktrace: Display the full stacktrace of the last error. 32 | 33 | Demo Command 34 | authentication: Authentication command 35 | echo: Echo command 36 | pojo: Pojo command 37 | table-complex: Complex table command 38 | table-simple: Simple table command 39 | 40 | Jmx Commands 41 | jmx-info: Displays information about jmx mbean. Use -a option to query attribute values. 42 | jmx-invoke: Invoke operation on object name. 43 | jmx-list: List jmx mbeans. 44 | 45 | Manage Sessions Commands 46 | * manage-sessions-info: Displays session 47 | * manage-sessions-list: Displays active sessions 48 | * manage-sessions-stop: Stop session 49 | 50 | System Commands 51 | system-env: List system environment. 52 | system-properties: List system properties. 53 | system-threads: Thread command. 54 | 55 | Commands marked with (*) are currently unavailable. 56 | Type `help ` to learn more. 57 | ``` 58 | -------------------------------------------------------------------------------- /samples/basic/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | ssh-shell-spring-boot-parent 21 | com.github.fonimus 22 | 3.1.1-SNAPSHOT 23 | ../../pom.xml 24 | 25 | 4.0.0 26 | 27 | ssh-shell-spring-boot-basic-sample 28 | Ssh shell spring boot basic sample 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | com.github.fonimus 45 | ssh-shell-spring-boot-starter 46 | ${project.version} 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | 56 | 57 | 58 | 59 | ${basedir}/src/main/resources 60 | 61 | *.yml 62 | *.txt 63 | 64 | true 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | ${spring-boot.version} 72 | 73 | 74 | repackage 75 | 76 | repackage 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /samples/basic/src/main/java/com/github/fonimus/ssh/shell/basic/BasicApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.basic; 18 | 19 | import org.springframework.boot.Banner; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.boot.builder.SpringApplicationBuilder; 22 | 23 | /** 24 | * Basic application example 25 | */ 26 | @SpringBootApplication 27 | public class BasicApplication { 28 | 29 | /** 30 | * Start basic application 31 | * 32 | * @param args main args 33 | */ 34 | public static void main(String[] args) { 35 | new SpringApplicationBuilder(BasicApplication.class).bannerMode(Banner.Mode.OFF).run(args); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/basic/src/main/java/com/github/fonimus/ssh/shell/basic/BasicController.java: -------------------------------------------------------------------------------- 1 | package com.github.fonimus.ssh.shell.basic; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class BasicController { 8 | 9 | @GetMapping("/ping") 10 | public String ping() { 11 | return "pong"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/basic/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.shell.interactive.enabled: false 2 | 3 | ssh: 4 | shell: 5 | prompt: 6 | color: cyan 7 | text: 'basic::>' 8 | password: password 9 | authorized-public-keys-file: samples/complete/src/main/resources/.ssh/authorized.keys 10 | commands: 11 | jmx: 12 | create: false 13 | script: 14 | create: false 15 | history: 16 | create: false 17 | postprocessors: 18 | create: false 19 | manage-sessions: 20 | create: false 21 | system: 22 | create: false 23 | 24 | logging: 25 | level: 26 | com.github.fonimus: debug 27 | -------------------------------------------------------------------------------- /samples/basic/src/test/java/com/github/fonimus/ssh/shell/basic/DemoApplicationWebTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.basic; 18 | 19 | import com.github.fonimus.ssh.shell.PromptColor; 20 | import com.github.fonimus.ssh.shell.SshShellHelper; 21 | import org.junit.jupiter.api.Test; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.test.context.SpringBootTest; 24 | 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertNotNull; 27 | 28 | @SpringBootTest(classes = BasicApplication.class, properties = { 29 | "spring.shell.interactive.enabled=false" 30 | }) 31 | public class DemoApplicationWebTest { 32 | 33 | @Autowired 34 | private BasicCommands demo; 35 | 36 | @Test 37 | void testApplicationStartup() { 38 | assertEquals("message", demo.echo("message", null)); 39 | assertEquals(SshShellHelper.getColoredMessage("message", PromptColor.CYAN), 40 | demo.echo("message", PromptColor.CYAN)); 41 | assertNotNull(demo.pojo()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/complete/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | 1. Build sample application 4 | 5 | ```bash 6 | mvn clean install -f samples/complete [-DskipTests] 7 | ``` 8 | 1. Start application 9 | 10 | ```bash 11 | java -jar samples/complete/target/ssh-shell-spring-boot-complete-sample[-version].jar 12 | ``` 13 | 1. Connect to application via ssh (default password: pass) 14 | 15 | ```bash 16 | ~/home$ ssh -p 2222 [user|actuator|admin]@localhost 17 | Password authentication 18 | Password: [password] 19 | 20 | _ _ _ _ 21 | _____| |_ __| |_ ___| | | 22 | (_-<_-< ' \ (_-< ' \/ -_) | | 23 | /__/__/_||_| /__/_||_\___|_|_| v1.5.0-SNAPSHOT 24 | 25 | 26 | Please type `help` to see available commands 27 | complete::>help 28 | AVAILABLE COMMANDS 29 | 30 | Actuator Commands 31 | * audit: Display audit endpoint. 32 | beans: Display beans endpoint. 33 | conditions: Display conditions endpoint. 34 | configprops: Display configprops endpoint. 35 | env: Display env endpoint. 36 | health: Display health endpoint. 37 | * httptrace: Display httptrace endpoint. 38 | info: Display info endpoint. 39 | loggers: Display or configure loggers. 40 | mappings: Display mappings endpoint. 41 | metrics: Display metrics endpoint. 42 | scheduledtasks: Display scheduledtasks endpoint. 43 | * sessions: Display sessions endpoint. 44 | shutdown: Shutdown application. 45 | * threaddump: Display threaddump endpoint. 46 | 47 | Built-In Commands 48 | clear: Clear the shell screen. 49 | exit, quit: Exit the shell. 50 | help: Display help about available commands. 51 | history: Display or save the history of previously run commands 52 | postprocessors: Display the available post processors 53 | script: Read and execute commands from a file. 54 | stacktrace: Display the full stacktrace of the last error. 55 | 56 | Datasource Commands 57 | datasource-list: List available datasources 58 | datasource-properties: Datasource properties command. Executes 'show variables' 59 | datasource-query: Datasource query command. 60 | datasource-update: Datasource update command. 61 | 62 | Demo Command 63 | admin: Admin command 64 | authentication: Authentication command 65 | conf: Confirmation command 66 | display-ssh-env: Displays ssh env information 67 | display-ssh-session: Displays ssh session information 68 | echo: Echo command 69 | ex: Ex command 70 | file: File command 71 | interactive: Interactive command 72 | progress: Progress command 73 | size: Terminal size command 74 | welcome: Welcome command 75 | 76 | Jmx Commands 77 | jmx-info: Displays information about jmx mbean. Use -a option to query attribute values. 78 | jmx-invoke: Invoke operation on object name. 79 | jmx-list: List jmx mbeans. 80 | 81 | Manage Sessions Commands 82 | manage-sessions-info: Displays session 83 | manage-sessions-list: Displays active sessions 84 | manage-sessions-stop: Stop session 85 | 86 | System Commands 87 | system-env: List system environment. 88 | system-properties: List system properties. 89 | system-threads: Thread command. 90 | 91 | Tasks Commands 92 | tasks-list: Display the available scheduled tasks 93 | tasks-restart: Restart all or specified task(s) 94 | tasks-stop: Stop all or specified task(s) 95 | 96 | Commands marked with (*) are currently unavailable. 97 | Type `help ` to learn more. 98 | ``` 99 | -------------------------------------------------------------------------------- /samples/complete/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | ssh-shell-spring-boot-parent 21 | com.github.fonimus 22 | 3.1.1-SNAPSHOT 23 | ../../pom.xml 24 | 25 | 4.0.0 26 | 27 | ssh-shell-spring-boot-complete-sample 28 | Ssh shell spring boot complete sample 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-security 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-actuator 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-jdbc 54 | 55 | 56 | com.github.fonimus 57 | ssh-shell-spring-boot-starter 58 | ${project.version} 59 | 60 | 61 | com.h2database 62 | h2 63 | 64 | 65 | com.mysql 66 | mysql-connector-j 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-test 72 | test 73 | 74 | 75 | 76 | 77 | 78 | 79 | ${basedir}/src/main/resources 80 | 81 | *.yml 82 | *.txt 83 | 84 | true 85 | 86 | 87 | ${basedir}/src/main/resources 88 | 89 | *.yml 90 | *.txt 91 | 92 | false 93 | 94 | 95 | 96 | 97 | org.springframework.boot 98 | spring-boot-maven-plugin 99 | ${spring-boot.version} 100 | 101 | 102 | repackage 103 | 104 | repackage 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.complete; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.scheduling.annotation.EnableScheduling; 22 | 23 | /** 24 | * Complete application example 25 | */ 26 | @SpringBootApplication 27 | @EnableScheduling 28 | public class CompleteApplication { 29 | 30 | /** 31 | * Start complete application 32 | * 33 | * @param args main args 34 | */ 35 | public static void main(String[] args) { 36 | SpringApplication.run(CompleteApplication.class, args); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteController.java: -------------------------------------------------------------------------------- 1 | package com.github.fonimus.ssh.shell.complete; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class CompleteController { 8 | 9 | @GetMapping("/ping") 10 | public String ping() { 11 | return "pong"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompletePromptProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.complete; 18 | 19 | import org.jline.utils.AttributedString; 20 | import org.springframework.shell.jline.PromptProvider; 21 | import org.springframework.stereotype.Component; 22 | 23 | import static org.jline.utils.AttributedStyle.CYAN; 24 | import static org.jline.utils.AttributedStyle.DEFAULT; 25 | 26 | @Component 27 | public class CompletePromptProvider implements PromptProvider { 28 | 29 | @Override 30 | public AttributedString getPrompt() { 31 | return new AttributedString("complete::>", DEFAULT.foreground(CYAN)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteSecurity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.complete; 18 | 19 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.security.authentication.AuthenticationManager; 23 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 24 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 25 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 26 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 27 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 28 | import org.springframework.security.crypto.password.PasswordEncoder; 29 | import org.springframework.security.web.SecurityFilterChain; 30 | 31 | /** 32 | * Security configuration 33 | */ 34 | @Configuration 35 | @EnableWebSecurity 36 | @EnableMethodSecurity 37 | public class CompleteSecurity { 38 | 39 | @Bean 40 | public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception { 41 | http.authorizeHttpRequests() 42 | .requestMatchers("/ping").permitAll() 43 | .requestMatchers(EndpointRequest.to("info")).permitAll() 44 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") 45 | .and().authenticationManager(authManager); 46 | return http.build(); 47 | } 48 | 49 | @Bean 50 | public PasswordEncoder passwordEncoder() { 51 | return new BCryptPasswordEncoder(); 52 | } 53 | 54 | @Bean 55 | public AuthenticationManager authManager(HttpSecurity http) throws Exception { 56 | AuthenticationManagerBuilder authenticationManagerBuilder = 57 | http.getSharedObject(AuthenticationManagerBuilder.class); 58 | authenticationManagerBuilder.inMemoryAuthentication() 59 | .withUser("user") 60 | .password(passwordEncoder().encode("password")) 61 | .roles("USER") 62 | .and() 63 | .withUser("actuator") 64 | .password(passwordEncoder().encode("password")) 65 | .roles("ACTUATOR") 66 | .and() 67 | .withUser("admin") 68 | .password(passwordEncoder().encode("admin")) 69 | .roles("ADMIN", "ACTUATOR"); 70 | return authenticationManagerBuilder.build(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /samples/complete/src/main/resources/.ssh/authorized.keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDlkW3hZVrrOPjcswznq5WDbF8IV3iEZo4EP2A9Ofl2dqqfKhgObDQuMUQN1OebBpUCo9W13CUle1ca7AdXJY4ZU62+YJCDKy9+hSz+KWfl7kJx+StL57sv3ProC3n7gAH+uedY6pSlHr6zTjsiEyGZbaKaSShFBX0ta3j+vZ11T+bxyK3j7BFJ2YcUa4ys+gphm3by/LV6xEhr3tohnFhfO5COKrEYqYC97EjsgDhcaFtpzjOnFoYC+HDCAFCwKWJj+Puu9qNj+NlT0gX4DuVFbWEEwH2ZB+qhk5/b75pAGLikRTK9lPLrTX0MX283lAWrD84d6xKWZaqrcxoP1HBnTCaqs5XGrXN81UP2EK+3KYNcNoIwN9x5UABOc1vDfK91IyYNgheVl8NT+T7TYNNj27piXFlvSgDx5dmuqBopYri+IqS17KJXaVgRjbdAwkWHNCqqSW9RoGDxUWlYgOh7KhNlN2m4AcW9YMEd1z2WWaYQpevYG3p3GGEZ8Av5Hbk= francois@MacBook-Pro-de-Francois.local 2 | -------------------------------------------------------------------------------- /samples/complete/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jmx: 3 | enabled: false 4 | first-datasource: 5 | url: jdbc:h2:mem:testdb 6 | second-datasource: 7 | url: jdbc:h2:mem:testdb2 8 | main.lazy-initialization: true 9 | 10 | ssh: 11 | shell: 12 | authentication: security 13 | authorized-public-keys: classpath:.ssh/authorized.keys 14 | commands: 15 | actuator: 16 | excludes: 17 | - audit 18 | jvm: 19 | enable: false 20 | threads: 21 | enable: false 22 | manage-sessions: 23 | enable: true 24 | datasource: 25 | excludes: 26 | host: 0.0.0.0 27 | shared-history: false 28 | history-directory: ./target 29 | 30 | management: 31 | endpoints: 32 | web: 33 | exposure: 34 | include: '*' 35 | endpoint: 36 | shutdown: 37 | enabled: true 38 | health: 39 | group: 40 | nocommands: 41 | include: '*' 42 | exclude: 43 | - demo-command 44 | commands: 45 | include: 46 | - demo-command 47 | info: 48 | build.enabled: true 49 | env.enabled: true 50 | git.enabled: true 51 | java.enabled: true 52 | os.enabled: true 53 | 54 | 55 | logging: 56 | level: 57 | com.github.fonimus: debug 58 | 59 | info: 60 | build: 61 | groupId: ${project.artifactId} 62 | artifactId: ${project.groupId} 63 | version: ${project.version} 64 | dependencies: 65 | spring-boot: ${spring-boot.version} 66 | spring-shell: ${spring-shell.version} 67 | -------------------------------------------------------------------------------- /samples/complete/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | _ _ _ _ 3 | _____| |_ __| |_ ___| | | 4 | (_-<_-< ' \ (_-< ' \/ -_) | | 5 | /__/__/_||_| /__/_||_\___|_|_| v${project.version} 6 | -------------------------------------------------------------------------------- /samples/complete/src/test/java/com/github/fonimus/ssh/shell/complete/AbstractDemoApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.complete; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.actuate.info.InfoEndpoint; 22 | 23 | import java.util.Map; 24 | 25 | import static org.junit.jupiter.api.Assertions.assertFalse; 26 | import static org.junit.jupiter.api.Assertions.assertTrue; 27 | 28 | public abstract class AbstractDemoApplicationTest { 29 | 30 | @Autowired 31 | private InfoEndpoint info; 32 | 33 | @Test 34 | void testApplicationStartup() { 35 | Map i = info.info(); 36 | assertFalse(i.isEmpty()); 37 | assertTrue(i.containsKey("build")); 38 | assertTrue(i.containsKey("dependencies")); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/complete/src/test/java/com/github/fonimus/ssh/shell/complete/DemoApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.complete; 18 | 19 | import org.springframework.boot.test.context.SpringBootTest; 20 | import org.springframework.test.annotation.DirtiesContext; 21 | 22 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { 23 | "ssh.shell.port=2346", 24 | "spring.shell.interactive.enabled=false" 25 | }) 26 | @DirtiesContext 27 | public class DemoApplicationTest 28 | extends AbstractDemoApplicationTest { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /samples/complete/src/test/java/com/github/fonimus/ssh/shell/complete/DemoApplicationWebEnvTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.complete; 18 | 19 | import org.springframework.boot.test.context.SpringBootTest; 20 | import org.springframework.test.annotation.DirtiesContext; 21 | 22 | @SpringBootTest(properties = { 23 | "ssh.shell.port=2345", 24 | "spring.shell.interactive.enabled=false" 25 | }) 26 | @DirtiesContext 27 | public class DemoApplicationWebEnvTest 28 | extends AbstractDemoApplicationTest { 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /samples/script.txt: -------------------------------------------------------------------------------- 1 | echo test1 2 | echo test2 3 | sleep 20 4 | echo test3 5 | -------------------------------------------------------------------------------- /starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | ssh-shell-spring-boot-parent 21 | com.github.fonimus 22 | 3.1.1-SNAPSHOT 23 | ../pom.xml 24 | 25 | 4.0.0 26 | 27 | ssh-shell-spring-boot-starter 28 | Ssh shell spring boot starter 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-autoconfigure 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-actuator 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-security 43 | true 44 | 45 | 46 | org.springframework.session 47 | spring-session-core 48 | true 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-configuration-processor 53 | true 54 | 55 | 56 | com.fasterxml.jackson.core 57 | jackson-databind 58 | true 59 | 60 | 61 | com.fasterxml.jackson.datatype 62 | jackson-datatype-jsr310 63 | true 64 | 65 | 66 | org.apache.sshd 67 | sshd-core 68 | 69 | 70 | org.springframework.shell 71 | spring-shell-starter 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-test 77 | test 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-starter 82 | test 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-web 87 | test 88 | 89 | 90 | org.springframework.session 91 | spring-session-jdbc 92 | test 93 | 94 | 95 | com.h2database 96 | h2 97 | test 98 | 99 | 100 | com.jcraft 101 | jsch 102 | 0.1.55 103 | test 104 | 105 | 106 | org.awaitility 107 | awaitility 108 | 4.2.1 109 | test 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedCompleterAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import lombok.AllArgsConstructor; 20 | import org.jline.reader.Candidate; 21 | import org.jline.reader.LineReader; 22 | import org.jline.reader.ParsedLine; 23 | import org.springframework.context.annotation.Primary; 24 | import org.springframework.shell.CompletingParsedLine; 25 | import org.springframework.shell.CompletionContext; 26 | import org.springframework.shell.CompletionProposal; 27 | import org.springframework.shell.Shell; 28 | import org.springframework.shell.boot.CompleterAutoConfiguration; 29 | import org.springframework.stereotype.Component; 30 | 31 | import java.util.List; 32 | import java.util.stream.Collectors; 33 | 34 | /** 35 | * Extended completer adapter to be able to set complete attribute of proposal 36 | *
37 | * Based on @{@link CompleterAutoConfiguration.CompleterAdapter} 38 | */ 39 | @Component 40 | @Primary 41 | @AllArgsConstructor 42 | public class ExtendedCompleterAdapter extends CompleterAutoConfiguration.CompleterAdapter { 43 | 44 | private final Shell shell; 45 | 46 | @Override 47 | public void complete(LineReader reader, ParsedLine line, List candidates) { 48 | CompletingParsedLine cpl = (line instanceof CompletingParsedLine) ? ((CompletingParsedLine) line) : t -> t; 49 | 50 | CompletionContext context = new CompletionContext(sanitizeInput(line.words()), line.wordIndex(), line.wordCursor(), null, null); 51 | 52 | List proposals = shell.complete(context); 53 | proposals.stream() 54 | .map(p -> new Candidate( 55 | p.dontQuote() ? p.value() : cpl.emit(p.value()).toString(), 56 | p.displayText(), 57 | p.category(), 58 | p.description(), 59 | null, 60 | null, 61 | isComplete(p)) 62 | ) 63 | .forEach(candidates::add); 64 | } 65 | 66 | private static boolean isComplete(CompletionProposal p) { 67 | if (p instanceof ExtendedCompletionProposal) { 68 | return ((ExtendedCompletionProposal) p).isComplete(); 69 | } 70 | return true; 71 | } 72 | 73 | /** 74 | * Sanitize the buffer input given the customizations applied to the JLine parser (e.g. support for 75 | * line continuations, etc.) 76 | * See @{@link CompleterAutoConfiguration} 77 | */ 78 | private static List sanitizeInput(List words) { 79 | words = words.stream() 80 | .map(s -> s.replaceAll("^\\n+|\\n+$", "")) // CR at beginning/end of line introduced by backslash continuation 81 | .map(s -> s.replaceAll("\\n+", " ")) // CR in middle of word introduced by return inside a quoted string 82 | .collect(Collectors.toList()); 83 | return words; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedCompletionProposal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | import org.springframework.shell.CompletionProposal; 22 | 23 | /** 24 | * Extended completion proposal to be able to set complete attribute of proposal 25 | */ 26 | public class ExtendedCompletionProposal extends CompletionProposal { 27 | 28 | /** 29 | * If should add space after proposed proposal 30 | */ 31 | @Getter 32 | @Setter 33 | private boolean complete; 34 | 35 | /** 36 | * Default constructor 37 | * 38 | * @param value string value 39 | * @param complete true if should add space after proposed proposal (true is default value when not using 40 | * extended completion proposal) 41 | */ 42 | public ExtendedCompletionProposal(String value, boolean complete) { 43 | super(value); 44 | this.complete = complete; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import org.springframework.shell.Input; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | /** 26 | * Extended input which takes in account special characters 27 | */ 28 | public class ExtendedInput implements Input { 29 | 30 | public static final String PIPE = "|"; 31 | 32 | public static final String ARROW = ">"; 33 | 34 | public static final List KEY_CHARS = Arrays.asList(PIPE, ARROW); 35 | 36 | private final Input base; 37 | 38 | /** 39 | * Default constructor 40 | * 41 | * @param base input base 42 | */ 43 | public ExtendedInput(Input base) { 44 | this.base = base; 45 | } 46 | 47 | private static boolean isKeyCharInLine(String str) { 48 | for (String key : KEY_CHARS) { 49 | if (str.contains(key)) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | 56 | @Override 57 | public String rawText() { 58 | String raw = base.rawText(); 59 | return raw != null && isKeyCharInLine(raw) ? raw.substring(0, firstIndexOfKeyChar(raw)) : raw; 60 | } 61 | 62 | @Override 63 | public List words() { 64 | List newList = new ArrayList<>(); 65 | for (String word : base.words()) { 66 | if (KEY_CHARS.contains(word)) { 67 | return newList; 68 | } 69 | newList.add(word); 70 | } 71 | return newList; 72 | } 73 | 74 | private int firstIndexOfKeyChar(String str) { 75 | int firstIndex = Integer.MAX_VALUE; 76 | for (String key : KEY_CHARS) { 77 | int keyIndex = str.indexOf(key); 78 | if (keyIndex > -1 && keyIndex < firstIndex) { 79 | firstIndex = keyIndex; 80 | } 81 | } 82 | return firstIndex; 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedInteractiveShellRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.fonimus.ssh.shell; 2 | 3 | import org.jline.reader.LineReader; 4 | import org.springframework.boot.ApplicationArguments; 5 | import org.springframework.context.annotation.Primary; 6 | import org.springframework.shell.Input; 7 | import org.springframework.shell.InputProvider; 8 | import org.springframework.shell.Shell; 9 | import org.springframework.shell.context.InteractionMode; 10 | import org.springframework.shell.context.ShellContext; 11 | import org.springframework.shell.jline.InteractiveShellRunner; 12 | import org.springframework.shell.jline.PromptProvider; 13 | import org.springframework.stereotype.Component; 14 | 15 | import static com.github.fonimus.ssh.shell.SshShellCommandFactory.SSH_THREAD_CONTEXT; 16 | 17 | /** 18 | * Used to clear thread context from post processors and also creates instance of InteractiveShellRunner 19 | * so that ThrowableResultHandler#shouldHandle() returns true 20 | */ 21 | @Component 22 | @Primary 23 | public class ExtendedInteractiveShellRunner extends InteractiveShellRunner { 24 | 25 | private final LineReader lineReader; 26 | private final PromptProvider promptProvider; 27 | private final Shell shell; 28 | private final ShellContext shellContext; 29 | 30 | public ExtendedInteractiveShellRunner(LineReader lineReader, PromptProvider promptProvider, Shell shell, ShellContext shellContext) { 31 | super(lineReader, promptProvider, shell, shellContext); 32 | this.lineReader = lineReader; 33 | this.promptProvider = promptProvider; 34 | this.shell = shell; 35 | this.shellContext = shellContext; 36 | } 37 | 38 | @Override 39 | public void run(ApplicationArguments args) throws Exception { 40 | shellContext.setInteractionMode(InteractionMode.INTERACTIVE); 41 | InputProvider inputProvider = new SshShellInputProvider(lineReader, promptProvider); 42 | shell.run(inputProvider); 43 | } 44 | 45 | @Override 46 | public boolean canRun(ApplicationArguments args) { 47 | return false; 48 | } 49 | 50 | public static class SshShellInputProvider 51 | extends InteractiveShellRunner.JLineInputProvider { 52 | 53 | public SshShellInputProvider(LineReader lineReader, PromptProvider promptProvider) { 54 | super(lineReader, promptProvider); 55 | } 56 | 57 | @Override 58 | public Input readInput() { 59 | SshContext ctx = SSH_THREAD_CONTEXT.get(); 60 | if (ctx != null) { 61 | ctx.getPostProcessorsList().clear(); 62 | } 63 | return super.readInput(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/PromptColor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | /** 20 | * Map enum color to jline ones 21 | */ 22 | public enum PromptColor { 23 | 24 | BLACK(0), 25 | RED(1), 26 | GREEN(2), 27 | YELLOW(3), 28 | BLUE(4), 29 | MAGENTA(5), 30 | CYAN(6), 31 | WHITE(7), 32 | BRIGHT(8); 33 | 34 | private final int value; 35 | 36 | PromptColor(int value) { 37 | this.value = value; 38 | } 39 | 40 | public int toJlineAttributedStyle() { 41 | return value; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/SimpleTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import lombok.Builder; 20 | import lombok.Data; 21 | import lombok.NonNull; 22 | import lombok.Singular; 23 | import org.springframework.shell.table.Aligner; 24 | import org.springframework.shell.table.BorderStyle; 25 | import org.springframework.shell.table.TableBuilder; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * Simple data builder, with header names, and list of lines, containing map with header names. 31 | * Optionally set aligner, and style 32 | */ 33 | @Data 34 | @Builder 35 | public class SimpleTable { 36 | 37 | @Singular 38 | private List columns; 39 | 40 | @Builder.Default 41 | private boolean displayHeaders = true; 42 | 43 | @Singular 44 | private List headerAligners; 45 | 46 | @NonNull 47 | @Singular 48 | private List> lines; 49 | 50 | @Singular 51 | private List lineAligners; 52 | 53 | @Builder.Default 54 | private boolean useFullBorder = true; 55 | 56 | @Builder.Default 57 | private BorderStyle borderStyle = BorderStyle.fancy_light; 58 | 59 | private SimpleTableBuilderListener tableBuilderListener; 60 | 61 | /** 62 | * Listener to add some properties to table builder before it is rendered 63 | */ 64 | @FunctionalInterface 65 | public interface SimpleTableBuilderListener { 66 | 67 | /** 68 | * Method called before render 69 | * 70 | * @param tableBuilder table builder 71 | */ 72 | void onBuilt(TableBuilder tableBuilder); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/SshContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.auth.SshAuthentication; 20 | import com.github.fonimus.ssh.shell.postprocess.PostProcessorObject; 21 | import lombok.Getter; 22 | import lombok.Setter; 23 | import org.apache.sshd.server.Environment; 24 | import org.apache.sshd.server.session.ServerSession; 25 | import org.jline.reader.LineReader; 26 | import org.jline.terminal.Terminal; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | /** 32 | * Ssh context to hold terminal, exit callback and thread per thread 33 | */ 34 | @Getter 35 | public class SshContext { 36 | 37 | private SshShellRunnable sshShellRunnable; 38 | 39 | private Terminal terminal; 40 | 41 | private LineReader lineReader; 42 | 43 | private SshAuthentication authentication; 44 | 45 | private final List postProcessorsList = new ArrayList<>(); 46 | 47 | @Setter 48 | private boolean background; 49 | 50 | private long backgroundCount = 0; 51 | 52 | /** 53 | * Default empty constructor 54 | */ 55 | public SshContext() { 56 | } 57 | 58 | /** 59 | * Constructor 60 | * 61 | * @param sshShellRunnable ssh runnable 62 | * @param terminal ssh terminal 63 | * @param lineReader ssh line reader 64 | * @param authentication (optional) spring authentication objects 65 | */ 66 | public SshContext(SshShellRunnable sshShellRunnable, Terminal terminal, LineReader lineReader, 67 | SshAuthentication authentication) { 68 | this.sshShellRunnable = sshShellRunnable; 69 | this.terminal = terminal; 70 | this.lineReader = lineReader; 71 | this.authentication = authentication; 72 | } 73 | 74 | /** 75 | * Check if current prompt is the one started with application 76 | * 77 | * @return if local prompt or not 78 | */ 79 | public boolean isLocalPrompt() { 80 | return sshShellRunnable == null; 81 | } 82 | 83 | /** 84 | * Return current ssh session 85 | * 86 | * @return ssh session, or null of is local prompt 87 | */ 88 | public ServerSession getSshSession() { 89 | return isLocalPrompt() ? null : sshShellRunnable.getSshSession(); 90 | } 91 | 92 | /** 93 | * Return current ssh env 94 | * 95 | * @return ssh env, or null of is local prompt 96 | */ 97 | public Environment getSshEnv() { 98 | return isLocalPrompt() ? null : sshShellRunnable.getSshEnv(); 99 | } 100 | 101 | /** 102 | * Increment background sessions count 103 | */ 104 | public void incrementBackgroundCount() { 105 | this.backgroundCount++; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/SshIO.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | import org.apache.sshd.server.ExitCallback; 22 | 23 | import java.io.InputStream; 24 | import java.io.OutputStream; 25 | 26 | /** 27 | * Ssh io 28 | */ 29 | @Getter 30 | @Setter 31 | public class SshIO { 32 | 33 | private InputStream is; 34 | 35 | private OutputStream os; 36 | 37 | private ExitCallback ec; 38 | } 39 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/SshShellUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import org.apache.sshd.common.channel.PtyMode; 20 | import org.jline.terminal.Attributes; 21 | 22 | import java.util.Map; 23 | 24 | /** 25 | * Utility tools 26 | */ 27 | public final class SshShellUtils { 28 | 29 | private SshShellUtils() { 30 | // private constructor 31 | } 32 | 33 | /** 34 | * Fill attributes with given modes 35 | * 36 | * @param attr attributes 37 | * @param ptyModes pty modes 38 | */ 39 | public static void fill(Attributes attr, Map ptyModes) { 40 | for (Map.Entry e : ptyModes.entrySet()) { 41 | switch (e.getKey()) { 42 | case VINTR -> 43 | attr.setControlChar(Attributes.ControlChar.VINTR, e.getValue()); 44 | case VQUIT -> 45 | attr.setControlChar(Attributes.ControlChar.VQUIT, e.getValue()); 46 | case VERASE -> 47 | attr.setControlChar(Attributes.ControlChar.VERASE, e.getValue()); 48 | case VKILL -> 49 | attr.setControlChar(Attributes.ControlChar.VKILL, e.getValue()); 50 | case VEOF -> 51 | attr.setControlChar(Attributes.ControlChar.VEOF, e.getValue()); 52 | case VEOL -> 53 | attr.setControlChar(Attributes.ControlChar.VEOL, e.getValue()); 54 | case VEOL2 -> 55 | attr.setControlChar(Attributes.ControlChar.VEOL2, e.getValue()); 56 | case VSTART -> 57 | attr.setControlChar(Attributes.ControlChar.VSTART, e.getValue()); 58 | case VSTOP -> 59 | attr.setControlChar(Attributes.ControlChar.VSTOP, e.getValue()); 60 | case VSUSP -> 61 | attr.setControlChar(Attributes.ControlChar.VSUSP, e.getValue()); 62 | case VDSUSP -> 63 | attr.setControlChar(Attributes.ControlChar.VDSUSP, e.getValue()); 64 | case VREPRINT -> 65 | attr.setControlChar(Attributes.ControlChar.VREPRINT, e.getValue()); 66 | case VWERASE -> 67 | attr.setControlChar(Attributes.ControlChar.VWERASE, e.getValue()); 68 | case VLNEXT -> 69 | attr.setControlChar(Attributes.ControlChar.VLNEXT, e.getValue()); 70 | case VSTATUS -> 71 | attr.setControlChar(Attributes.ControlChar.VSTATUS, e.getValue()); 72 | case VDISCARD -> 73 | attr.setControlChar(Attributes.ControlChar.VDISCARD, e.getValue()); 74 | case ECHO -> 75 | attr.setLocalFlag(Attributes.LocalFlag.ECHO, e.getValue() != 0); 76 | case ICANON -> 77 | attr.setLocalFlag(Attributes.LocalFlag.ICANON, e.getValue() != 0); 78 | case ISIG -> 79 | attr.setLocalFlag(Attributes.LocalFlag.ISIG, e.getValue() != 0); 80 | case ICRNL -> 81 | attr.setInputFlag(Attributes.InputFlag.ICRNL, e.getValue() != 0); 82 | case INLCR -> 83 | attr.setInputFlag(Attributes.InputFlag.INLCR, e.getValue() != 0); 84 | case IGNCR -> 85 | attr.setInputFlag(Attributes.InputFlag.IGNCR, e.getValue() != 0); 86 | case OCRNL -> 87 | attr.setOutputFlag(Attributes.OutputFlag.OCRNL, e.getValue() != 0); 88 | case ONLCR -> 89 | attr.setOutputFlag(Attributes.OutputFlag.ONLCR, e.getValue() != 0); 90 | case ONLRET -> 91 | attr.setOutputFlag(Attributes.OutputFlag.ONLRET, e.getValue() != 0); 92 | case OPOST -> 93 | attr.setOutputFlag(Attributes.OutputFlag.OPOST, e.getValue() != 0); 94 | default -> { 95 | } 96 | // nothing to do 97 | } 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/auth/SshAuthentication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.auth; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Getter; 21 | import lombok.NonNull; 22 | import lombok.RequiredArgsConstructor; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Ssh authentication 28 | */ 29 | @Getter 30 | @RequiredArgsConstructor 31 | @AllArgsConstructor 32 | public class SshAuthentication { 33 | 34 | @NonNull 35 | private final String name; 36 | 37 | @NonNull 38 | private final Object principal; 39 | 40 | private Object details; 41 | 42 | private Object credentials; 43 | 44 | private List authorities; 45 | } 46 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/auth/SshShellAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.auth; 18 | 19 | import org.apache.sshd.server.auth.password.PasswordAuthenticator; 20 | 21 | /** 22 | * Interface to implements custom authentication provider 23 | */ 24 | @FunctionalInterface 25 | public interface SshShellAuthenticationProvider 26 | extends PasswordAuthenticator { 27 | 28 | String AUTHENTICATION_ATTRIBUTE = "authentication"; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/auth/SshShellPasswordAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.auth; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.apache.sshd.server.auth.password.PasswordChangeRequiredException; 21 | import org.apache.sshd.server.session.ServerSession; 22 | 23 | import java.util.UUID; 24 | 25 | /** 26 | * Password implementation 27 | */ 28 | @Slf4j 29 | public class SshShellPasswordAuthenticationProvider 30 | implements SshShellAuthenticationProvider { 31 | 32 | private final String user; 33 | 34 | private final String password; 35 | 36 | public SshShellPasswordAuthenticationProvider(String user, String password) { 37 | this.user = user; 38 | String pass = password; 39 | if (pass == null) { 40 | pass = UUID.randomUUID().toString(); 41 | LOGGER.info(" --- Generating password for ssh connection: {}", pass); 42 | } 43 | this.password = pass; 44 | } 45 | 46 | @Override 47 | public boolean authenticate(String username, String pass, 48 | ServerSession serverSession) throws PasswordChangeRequiredException { 49 | 50 | serverSession.getIoSession().setAttribute(AUTHENTICATION_ATTRIBUTE, new SshAuthentication(username, username)); 51 | 52 | return username.equals(this.user) && pass.equals(this.password); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/auth/SshShellPublicKeyAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.auth; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator; 21 | import org.apache.sshd.server.session.ServerSession; 22 | 23 | import java.io.File; 24 | import java.security.PublicKey; 25 | 26 | import static com.github.fonimus.ssh.shell.auth.SshShellAuthenticationProvider.AUTHENTICATION_ATTRIBUTE; 27 | 28 | /** 29 | * Authorized keys authenticator extension to set authentication attribute 30 | */ 31 | @Slf4j 32 | public class SshShellPublicKeyAuthenticationProvider 33 | extends AuthorizedKeysAuthenticator { 34 | 35 | /** 36 | * Default constructor 37 | * 38 | * @param publicKeysFile public keys file 39 | */ 40 | public SshShellPublicKeyAuthenticationProvider(File publicKeysFile) { 41 | super(publicKeysFile.toPath()); 42 | } 43 | 44 | @Override 45 | public boolean authenticate(String username, PublicKey key, ServerSession session) { 46 | boolean authenticated = super.authenticate(username, key, session); 47 | session.getIoSession().setAttribute(AUTHENTICATION_ATTRIBUTE, new SshAuthentication(username, username)); 48 | return authenticated; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/AbstractCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellCommandFactory; 20 | import com.github.fonimus.ssh.shell.SshShellHelper; 21 | import com.github.fonimus.ssh.shell.SshShellProperties; 22 | import com.github.fonimus.ssh.shell.auth.SshAuthentication; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.springframework.shell.Availability; 25 | import org.springframework.shell.standard.AbstractShellComponent; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * Abstract command with availability 31 | */ 32 | @Slf4j 33 | public class AbstractCommand extends AbstractShellComponent { 34 | 35 | protected final SshShellHelper helper; 36 | 37 | protected final SshShellProperties properties; 38 | 39 | protected final CommandProperties commandProperties; 40 | 41 | public AbstractCommand(SshShellHelper helper, SshShellProperties properties, CommandProperties commandProperties) { 42 | this.helper = helper; 43 | this.properties = properties; 44 | this.commandProperties = commandProperties; 45 | } 46 | 47 | /** 48 | * Compute availability depending on command group and name 49 | * 50 | * @param commandGroup command group 51 | * @param commandName command name 52 | * @return command availability 53 | */ 54 | protected Availability availability(String commandGroup, String commandName) { 55 | try { 56 | preAvailability(); 57 | if (!commandProperties.isEnable()) { 58 | return Availability.unavailable("command deactivated (please check property '" + 59 | SshShellProperties.SSH_SHELL_PREFIX + ".commands." + commandGroup + ".enable" + "')"); 60 | } 61 | if (commandProperties.getExcludes() != null && commandProperties.getExcludes().contains(commandName)) { 62 | return Availability.unavailable("command is excluded (please check property '" + 63 | SshShellProperties.SSH_SHELL_PREFIX + ".commands." + commandGroup + ".excludes" + "')"); 64 | } 65 | if (commandProperties.getIncludes() != null && !commandProperties.getIncludes().contains(commandName)) { 66 | return Availability.unavailable("command not included (please check property '" + 67 | SshShellProperties.SSH_SHELL_PREFIX + ".commands." + commandGroup + ".includes" + "')"); 68 | } 69 | if (helper.isLocalPrompt()) { 70 | LOGGER.debug("Not an ssh session -> local prompt -> giving all rights"); 71 | return Availability.available(); 72 | } 73 | SshAuthentication auth = SshShellCommandFactory.SSH_THREAD_CONTEXT.get().getAuthentication(); 74 | List authorities = auth != null ? auth.getAuthorities() : null; 75 | if (commandProperties.isRestricted() && !helper.checkAuthorities(commandProperties.getAuthorizedRoles(), 76 | authorities, properties.getAuthentication() == SshShellProperties.AuthenticationType.simple)) { 77 | return Availability.unavailable("command is forbidden for current user"); 78 | } 79 | postAvailability(); 80 | return Availability.available(); 81 | } catch (AvailabilityException e) { 82 | return Availability.unavailable(e.getMessage()); 83 | } 84 | } 85 | 86 | /** 87 | * Extends this to add behavior before the one in abstract 88 | * 89 | * @throws AvailabilityException if unavailable 90 | */ 91 | protected void preAvailability() throws AvailabilityException { 92 | // nothing by default 93 | } 94 | 95 | 96 | /** 97 | * Extends this to add behavior after the one in abstract 98 | * 99 | * @throws AvailabilityException if unavailable 100 | */ 101 | protected void postAvailability() throws AvailabilityException { 102 | // nothing by default 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/AvailabilityException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import java.io.Serial; 20 | 21 | /** 22 | * Availability 23 | */ 24 | public class AvailabilityException 25 | extends Exception { 26 | 27 | @Serial 28 | private static final long serialVersionUID = -8323343028474225670L; 29 | 30 | public AvailabilityException(String message) { 31 | super(message); 32 | } 33 | 34 | public AvailabilityException(String message, Throwable cause) { 35 | super(message, cause); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/ColorAligner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.PromptColor; 20 | import com.github.fonimus.ssh.shell.SshShellHelper; 21 | import org.springframework.shell.table.Aligner; 22 | 23 | /** 24 | * Add this aligner to color cell 25 | */ 26 | public class ColorAligner implements Aligner { 27 | 28 | private final PromptColor color; 29 | 30 | /** 31 | * Default constructor 32 | * 33 | * @param color the cell text color 34 | */ 35 | public ColorAligner(PromptColor color) { 36 | this.color = color; 37 | } 38 | 39 | @Override 40 | public String[] align(String[] text, int cellWidth, int cellHeight) { 41 | String[] result = new String[text.length]; 42 | for (int i = 0; i < text.length; i++) { 43 | result[i] = SshShellHelper.getColoredMessage(text[i], color); 44 | } 45 | return result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/CommandProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import static com.github.fonimus.ssh.shell.SshShellProperties.ADMIN_ROLE; 28 | 29 | /** 30 | * Command specific properties 31 | */ 32 | @Data 33 | @NoArgsConstructor 34 | @AllArgsConstructor 35 | public class CommandProperties { 36 | 37 | /** 38 | * Create command at startup 39 | */ 40 | private boolean create = true; 41 | 42 | /** 43 | * Enable command at startup 44 | */ 45 | private boolean enable = true; 46 | 47 | /** 48 | * Is command restricted 49 | */ 50 | private boolean restricted = true; 51 | 52 | private List authorizedRoles = new ArrayList<>(Collections.singletonList(ADMIN_ROLE)); 53 | 54 | /** 55 | * Possibility to include some sub commands only 56 | */ 57 | private List includes; 58 | 59 | /** 60 | * Possibility to exclude some sub commands only 61 | */ 62 | private List excludes; 63 | 64 | /** 65 | * Create properties for disabled command 66 | * 67 | * @return disabled command 68 | */ 69 | public static CommandProperties disabledByDefault() { 70 | CommandProperties properties = new CommandProperties(); 71 | properties.setEnable(false); 72 | return properties; 73 | } 74 | 75 | /** 76 | * Create properties for command with authorized roles 77 | * 78 | * @return command with authorized roles 79 | */ 80 | public static CommandProperties withAuthorizedRoles(List authorizedRoles) { 81 | CommandProperties properties = new CommandProperties(); 82 | properties.setAuthorizedRoles(authorizedRoles); 83 | return properties; 84 | } 85 | 86 | /** 87 | * Create properties for not restructed command 88 | * 89 | * @return not restructed command 90 | */ 91 | public static CommandProperties notRestrictedByDefault() { 92 | CommandProperties properties = new CommandProperties(); 93 | properties.setRestricted(false); 94 | properties.setAuthorizedRoles(null); 95 | return properties; 96 | } 97 | 98 | /** 99 | * Create properties for excluded by default 100 | * 101 | * @return excluded by default 102 | */ 103 | public static CommandProperties withExcludedByDefault(List excludes) { 104 | CommandProperties properties = new CommandProperties(); 105 | properties.setExcludes(excludes); 106 | return properties; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/HistoryCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellHelper; 20 | import com.github.fonimus.ssh.shell.SshShellProperties; 21 | import com.github.fonimus.ssh.shell.providers.ExtendedFileValueProvider; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 | import org.springframework.shell.Availability; 25 | import org.springframework.shell.standard.ShellCommandGroup; 26 | import org.springframework.shell.standard.ShellMethod; 27 | import org.springframework.shell.standard.ShellMethodAvailability; 28 | import org.springframework.shell.standard.ShellOption; 29 | import org.springframework.shell.standard.commands.History; 30 | import org.springframework.util.CollectionUtils; 31 | 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.util.List; 35 | 36 | /** 37 | * Override history command to get history per user if not shared 38 | */ 39 | @Slf4j 40 | @SshShellComponent 41 | @ShellCommandGroup("Built-In Commands") 42 | @ConditionalOnProperty( 43 | name = SshShellProperties.SSH_SHELL_PREFIX + ".commands." + HistoryCommand.GROUP + ".create", 44 | havingValue = "true", matchIfMissing = true 45 | ) 46 | public class HistoryCommand extends AbstractCommand implements History.Command { 47 | 48 | public static final String GROUP = "history"; 49 | public static final String COMMAND_HISTORY = GROUP; 50 | 51 | public HistoryCommand(SshShellProperties properties, SshShellHelper helper) { 52 | super(helper, properties, properties.getCommands().getHistory()); 53 | } 54 | 55 | @ShellMethod(key = COMMAND_HISTORY, value = "Display or save the history of previously run commands") 56 | @ShellMethodAvailability("historyAvailability") 57 | public Object history( 58 | @ShellOption(help = "A file to save history to.", defaultValue = ShellOption.NULL, valueProvider = ExtendedFileValueProvider.class) File file, 59 | @ShellOption(help = "To display standard spring shell way (array.tostring). Default value: false", defaultValue = "false") boolean displayArray 60 | ) throws IOException { 61 | List result = new History(helper.getHistory()).history(file); 62 | if (file != null && result.size() == 1) { 63 | return result.get(0); 64 | } else if (displayArray) { 65 | return result; 66 | } 67 | StringBuilder sb = new StringBuilder(); 68 | if (!CollectionUtils.isEmpty(result)) { 69 | result.forEach(h -> sb.append(h).append(System.lineSeparator())); 70 | } 71 | return sb.toString(); 72 | } 73 | 74 | private Availability historyAvailability() { 75 | return availability(GROUP, COMMAND_HISTORY); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/PostProcessorsCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellHelper; 20 | import com.github.fonimus.ssh.shell.SshShellProperties; 21 | import com.github.fonimus.ssh.shell.postprocess.PostProcessor; 22 | import org.jline.utils.AttributedStringBuilder; 23 | import org.jline.utils.AttributedStyle; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.shell.Availability; 26 | import org.springframework.shell.standard.ShellCommandGroup; 27 | import org.springframework.shell.standard.ShellMethod; 28 | import org.springframework.shell.standard.ShellMethodAvailability; 29 | 30 | import java.lang.reflect.ParameterizedType; 31 | import java.util.ArrayList; 32 | import java.util.Comparator; 33 | import java.util.List; 34 | 35 | /** 36 | * Command to list available post processors 37 | */ 38 | @SshShellComponent 39 | @ShellCommandGroup("Built-In Commands") 40 | @ConditionalOnProperty( 41 | name = SshShellProperties.SSH_SHELL_PREFIX + ".commands." + PostProcessorsCommand.GROUP + ".create", 42 | havingValue = "true", matchIfMissing = true 43 | ) 44 | public class PostProcessorsCommand extends AbstractCommand { 45 | 46 | public static final String GROUP = "postprocessors"; 47 | public static final String COMMAND_POST_PROCESSORS = "postprocessors"; 48 | 49 | private final List> postProcessors; 50 | 51 | public PostProcessorsCommand(SshShellHelper helper, SshShellProperties properties, 52 | List> postProcessors) { 53 | super(helper, properties, properties.getCommands().getPostprocessors()); 54 | this.postProcessors = new ArrayList<>(postProcessors); 55 | this.postProcessors.sort(Comparator.comparing(PostProcessor::getName)); 56 | } 57 | 58 | @ShellMethod(key = COMMAND_POST_PROCESSORS, value = "Display the available post processors") 59 | @ShellMethodAvailability("postprocessorsAvailability") 60 | public CharSequence postprocessors() { 61 | AttributedStringBuilder result = new AttributedStringBuilder(); 62 | result.append("Available Post-Processors\n\n", AttributedStyle.BOLD); 63 | for (PostProcessor postProcessor : postProcessors) { 64 | result.append("\t" + postProcessor.getName() + ":\n", AttributedStyle.BOLD); 65 | Class input = 66 | ((Class) ((ParameterizedType) (postProcessor.getClass().getGenericInterfaces())[0]).getActualTypeArguments()[0]); 67 | Class output = 68 | ((Class) ((ParameterizedType) (postProcessor.getClass().getGenericInterfaces())[0]).getActualTypeArguments()[1]); 69 | result.append("\t\thelp : " + postProcessor.getDescription() + "\n", AttributedStyle.DEFAULT); 70 | result.append("\t\tinput : " + input.getName() + "\n", AttributedStyle.DEFAULT); 71 | result.append("\t\toutput : " + output.getName() + "\n", AttributedStyle.DEFAULT); 72 | } 73 | 74 | return result; 75 | } 76 | 77 | private Availability postprocessorsAvailability() { 78 | return availability(GROUP, COMMAND_POST_PROCESSORS); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/SshShellComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 20 | import org.springframework.shell.standard.ShellComponent; 21 | 22 | import java.lang.annotation.*; 23 | 24 | import static com.github.fonimus.ssh.shell.SshShellProperties.SSH_SHELL_ENABLE; 25 | 26 | /** 27 | * Conditional {@link org.springframework.shell.standard.ShellComponent} 28 | */ 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @Target(ElementType.TYPE) 31 | @Documented 32 | @ShellComponent 33 | @ConditionalOnProperty(name = SSH_SHELL_ENABLE, havingValue = "true", matchIfMissing = true) 34 | public @interface SshShellComponent { 35 | 36 | /** 37 | * Used to indicate a suggestion for a logical name for the component. 38 | * 39 | * @return the suggested component name, if any 40 | */ 41 | String value() default ""; 42 | } 43 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/commands/StacktraceCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellHelper; 20 | import com.github.fonimus.ssh.shell.SshShellProperties; 21 | import com.github.fonimus.ssh.shell.postprocess.ExtendedResultHandlerService; 22 | import org.jline.terminal.Terminal; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.context.annotation.Lazy; 26 | import org.springframework.shell.Availability; 27 | import org.springframework.shell.standard.ShellCommandGroup; 28 | import org.springframework.shell.standard.ShellMethod; 29 | import org.springframework.shell.standard.ShellMethodAvailability; 30 | import org.springframework.shell.standard.commands.Stacktrace; 31 | 32 | /** 33 | * Override stacktrace command to get error per thread 34 | */ 35 | @SshShellComponent 36 | @ShellCommandGroup("Built-In Commands") 37 | @ConditionalOnProperty( 38 | name = SshShellProperties.SSH_SHELL_PREFIX + ".commands." + StacktraceCommand.GROUP + ".create", 39 | havingValue = "true", matchIfMissing = true 40 | ) 41 | public class StacktraceCommand extends AbstractCommand implements Stacktrace.Command { 42 | 43 | public static final String GROUP = "stacktrace"; 44 | public static final String COMMAND_STACKTRACE = GROUP; 45 | 46 | private Terminal terminal; 47 | 48 | public StacktraceCommand(SshShellHelper helper, SshShellProperties properties) { 49 | super(helper, properties, properties.getCommands().getStacktrace()); 50 | } 51 | 52 | @ShellMethod(key = COMMAND_STACKTRACE, value = "Display the full stacktrace of the last error.") 53 | @ShellMethodAvailability("stacktraceAvailability") 54 | public void stacktrace() { 55 | Throwable lastError = ExtendedResultHandlerService.THREAD_CONTEXT.get(); 56 | if (lastError != null) { 57 | lastError.printStackTrace(this.terminal.writer()); 58 | } 59 | } 60 | 61 | @Autowired 62 | @Lazy 63 | public void setTerminal(Terminal terminal) { 64 | this.terminal = terminal; 65 | } 66 | 67 | private Availability stacktraceAvailability() { 68 | return availability(GROUP, COMMAND_STACKTRACE); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/interactive/Interactive.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.interactive; 18 | 19 | import lombok.Builder; 20 | import lombok.Getter; 21 | import lombok.NonNull; 22 | import lombok.Singular; 23 | import org.jline.terminal.Size; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * Interactive bean 29 | */ 30 | @Builder 31 | @Getter 32 | public class Interactive { 33 | 34 | @NonNull 35 | private InteractiveInput input; 36 | 37 | @Builder.Default 38 | private long refreshDelay = 3000; 39 | 40 | @Builder.Default 41 | private boolean fullScreen = true; 42 | 43 | @Builder.Default 44 | private boolean exit = true; 45 | 46 | @Builder.Default 47 | private boolean increase = true; 48 | 49 | @Builder.Default 50 | private boolean decrease = true; 51 | 52 | @Singular 53 | private List bindings; 54 | 55 | private Size size; 56 | } 57 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/interactive/InteractiveInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.interactive; 18 | 19 | import org.jline.terminal.Size; 20 | import org.jline.utils.AttributedString; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * Interface to give to interactive command to provide lines 26 | */ 27 | @FunctionalInterface 28 | public interface InteractiveInput { 29 | 30 | /** 31 | * Get lines to write 32 | * 33 | * @param size terminal size 34 | * @param currentDelay current refresh delay 35 | * @return lines 36 | */ 37 | List getLines(Size size, long currentDelay); 38 | } 39 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/interactive/InteractiveInputIO.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.interactive; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.NonNull; 22 | import lombok.RequiredArgsConstructor; 23 | import org.jline.utils.AttributedString; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * Interface to give to interactive command to provide lines 29 | */ 30 | @Data 31 | @RequiredArgsConstructor 32 | @AllArgsConstructor 33 | public class InteractiveInputIO { 34 | 35 | private boolean stop; 36 | 37 | @NonNull 38 | private List lines; 39 | } 40 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/interactive/KeyBinding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.interactive; 18 | 19 | import lombok.Builder; 20 | import lombok.Getter; 21 | import lombok.NonNull; 22 | import lombok.Singular; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Key binding bean 28 | */ 29 | @Builder 30 | @Getter 31 | public class KeyBinding { 32 | 33 | @NonNull 34 | private String description; 35 | 36 | @NonNull 37 | private KeyBindingInput input; 38 | 39 | @NonNull 40 | @Singular 41 | private List keys; 42 | } 43 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/interactive/KeyBindingInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.interactive; 18 | 19 | /** 20 | * Key binding input interface 21 | */ 22 | @FunctionalInterface 23 | public interface KeyBindingInput { 24 | 25 | /** 26 | * Perform action 27 | */ 28 | void action(); 29 | } 30 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/interactive/StoppableInteractiveInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.interactive; 18 | 19 | import org.jline.terminal.Size; 20 | import org.jline.utils.AttributedString; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * Interface to give to interactive command to provide lines 26 | */ 27 | @FunctionalInterface 28 | public interface StoppableInteractiveInput extends InteractiveInput { 29 | 30 | /** 31 | * Get lines to write 32 | * 33 | * @param size terminal size 34 | * @param currentDelay current refresh delay 35 | * @return bean containing lines 36 | */ 37 | InteractiveInputIO getIO(Size size, long currentDelay); 38 | 39 | @Override 40 | default List getLines(Size size, long currentDelay) { 41 | return getIO(size, currentDelay).getLines(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.listeners; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import org.apache.sshd.server.channel.ChannelSession; 22 | 23 | /** 24 | * Ssh shell event 25 | */ 26 | @Data 27 | @AllArgsConstructor 28 | public class SshShellEvent { 29 | 30 | private SshShellEventType type; 31 | 32 | private ChannelSession session; 33 | 34 | public long getSessionId() { 35 | return session.getServerSession().getIoSession().getId(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellEventType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.listeners; 18 | 19 | /** 20 | * Event types 21 | */ 22 | public enum SshShellEventType { 23 | SESSION_STARTED, SESSION_STOPPED, SESSION_STOPPED_UNEXPECTEDLY 24 | } 25 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.listeners; 18 | 19 | /** 20 | * Listen for ssh shell events 21 | */ 22 | public interface SshShellListener { 23 | 24 | /** 25 | * Event occurred 26 | * 27 | * @param event ssh shell event 28 | */ 29 | void onEvent(SshShellEvent event); 30 | } 31 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellListenerService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.listeners; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.apache.sshd.server.channel.ChannelSession; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * Service calling listeners 27 | */ 28 | @Slf4j 29 | public class SshShellListenerService { 30 | 31 | private final List listeners; 32 | 33 | public SshShellListenerService(List listeners) { 34 | this.listeners = listeners == null ? new ArrayList<>() : listeners; 35 | LOGGER.info("Ssh shell listener service initialized with {} listeners", this.listeners.size()); 36 | } 37 | 38 | /** 39 | * Session started 40 | * 41 | * @param channelSession ssh channel session 42 | */ 43 | public void onSessionStarted(ChannelSession channelSession) { 44 | notify(new SshShellEvent(SshShellEventType.SESSION_STARTED, channelSession)); 45 | } 46 | 47 | /** 48 | * Session stopped 49 | * 50 | * @param channelSession ssh channel session 51 | */ 52 | public void onSessionStopped(ChannelSession channelSession) { 53 | notify(new SshShellEvent(SshShellEventType.SESSION_STOPPED, channelSession)); 54 | } 55 | 56 | /** 57 | * Session stopped with error 58 | * 59 | * @param channelSession ssh channel session 60 | */ 61 | public void onSessionError(ChannelSession channelSession) { 62 | notify(new SshShellEvent(SshShellEventType.SESSION_STOPPED_UNEXPECTEDLY, channelSession)); 63 | } 64 | 65 | private void notify(SshShellEvent event) { 66 | for (SshShellListener listener : this.listeners) { 67 | try { 68 | listener.onEvent(event); 69 | } catch (RuntimeException e) { 70 | LOGGER.error("Unable to execute onSessionStarted on listener : {}", listener.getClass().getName(), e); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/manage/SshShellSessionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.manage; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellCommandFactory; 20 | import com.github.fonimus.ssh.shell.auth.SshAuthentication; 21 | import org.apache.sshd.server.channel.ChannelSession; 22 | import org.springframework.stereotype.Component; 23 | 24 | import java.util.Map; 25 | 26 | /** 27 | * Session manager 28 | */ 29 | @Component 30 | public class SshShellSessionManager { 31 | 32 | private final SshShellCommandFactory commandFactory; 33 | 34 | /** 35 | * Ssh shell session manager 36 | * 37 | * @param commandFactory ssh shell command factory 38 | */ 39 | public SshShellSessionManager(SshShellCommandFactory commandFactory) { 40 | this.commandFactory = commandFactory; 41 | } 42 | 43 | /** 44 | * List active sessions 45 | * 46 | * @return active sessions 47 | */ 48 | public Map listSessions() { 49 | return this.commandFactory.listSessions(); 50 | } 51 | 52 | /** 53 | * @param id session id 54 | * @return found session, or null if not existing 55 | */ 56 | public ChannelSession getSession(long id) { 57 | return listSessions().get(id); 58 | } 59 | 60 | /** 61 | * Stop active session 62 | * 63 | * @param id session id 64 | * @return true if session found and stopped, false otherwise 65 | */ 66 | public boolean stopSession(long id) { 67 | ChannelSession session = getSession(id); 68 | if (session != null) { 69 | this.commandFactory.destroy(session); 70 | return true; 71 | } 72 | return false; 73 | } 74 | 75 | /** 76 | * Search for authenticated user in session 77 | * 78 | * @param session ssh session 79 | * @return user name 80 | */ 81 | public static String sessionUserName(ChannelSession session) { 82 | SshAuthentication authentication = 83 | (SshAuthentication) session.getServerSession().getIoSession().getAttribute("authentication"); 84 | if (authentication == null) { 85 | return null; 86 | } 87 | return authentication.getName(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * Post processor interface 23 | */ 24 | public interface PostProcessor { 25 | 26 | String getName(); 27 | 28 | String getDescription(); 29 | 30 | O process(I result, List parameters) throws PostProcessorException; 31 | } 32 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import java.io.Serial; 20 | 21 | /** 22 | * Post processor exception 23 | */ 24 | public class PostProcessorException 25 | extends Exception { 26 | 27 | @Serial 28 | private static final long serialVersionUID = 150227794242813079L; 29 | 30 | public PostProcessorException(String message) { 31 | super(message); 32 | } 33 | 34 | public PostProcessorException(String message, Throwable cause) { 35 | super(message, cause); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessorObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Getter; 21 | import lombok.NonNull; 22 | import lombok.RequiredArgsConstructor; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Post processor object 28 | */ 29 | @RequiredArgsConstructor 30 | @AllArgsConstructor 31 | @Getter 32 | public class PostProcessorObject { 33 | 34 | @NonNull 35 | private String name; 36 | 37 | private List parameters; 38 | } 39 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/GrepPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess.provided; 18 | 19 | import com.github.fonimus.ssh.shell.postprocess.PostProcessor; 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * Grep post processor 26 | */ 27 | @Slf4j 28 | public class GrepPostProcessor 29 | implements PostProcessor { 30 | 31 | @Override 32 | public String getName() { 33 | return "grep"; 34 | } 35 | 36 | @Override 37 | public String getDescription() { 38 | return "Find pattern in result lines"; 39 | } 40 | 41 | @Override 42 | public String process(String result, List parameters) { 43 | if (parameters == null || parameters.isEmpty()) { 44 | LOGGER.debug("Cannot use [{}] post processor without any parameters", getName()); 45 | return result; 46 | } else { 47 | StringBuilder sb = new StringBuilder(); 48 | for (String line : result.split("\n")) { 49 | if (contains(line, parameters)) { 50 | sb.append(line).append("\n"); 51 | } 52 | } 53 | return sb.toString().isEmpty() ? sb.toString() : sb.substring(0, sb.toString().length() - 1); 54 | } 55 | } 56 | 57 | private boolean contains(String line, List parameters) { 58 | for (String parameter : parameters) { 59 | if (parameter == null || parameter.isEmpty() || line.contains(parameter)) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/HighlightPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess.provided; 18 | 19 | import com.github.fonimus.ssh.shell.PromptColor; 20 | import com.github.fonimus.ssh.shell.SshShellHelper; 21 | import com.github.fonimus.ssh.shell.postprocess.PostProcessor; 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Grep post processor 28 | */ 29 | @Slf4j 30 | public class HighlightPostProcessor 31 | implements PostProcessor { 32 | 33 | @Override 34 | public String getName() { 35 | return "highlight"; 36 | } 37 | 38 | @Override 39 | public String getDescription() { 40 | return "Highlight some words in result"; 41 | } 42 | 43 | @Override 44 | public String process(String result, List parameters) { 45 | if (parameters == null || parameters.isEmpty()) { 46 | LOGGER.debug("Cannot use [{}] post processor without any parameters", getName()); 47 | return result; 48 | } else { 49 | String finalResult = result; 50 | for (String toHighlight : parameters) { 51 | finalResult = finalResult.replaceAll(toHighlight, 52 | SshShellHelper.getBackgroundColoredMessage(toHighlight, 53 | PromptColor.YELLOW)); 54 | } 55 | return finalResult; 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/JsonPointerPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess.provided; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.github.fonimus.ssh.shell.postprocess.PostProcessor; 22 | import lombok.AllArgsConstructor; 23 | import lombok.extern.slf4j.Slf4j; 24 | 25 | import java.io.IOException; 26 | import java.util.List; 27 | 28 | /** 29 | * Json pointer post processor 30 | */ 31 | @Slf4j 32 | @AllArgsConstructor 33 | public class JsonPointerPostProcessor 34 | implements PostProcessor { 35 | 36 | private final ObjectMapper mapper; 37 | 38 | @Override 39 | public String getName() { 40 | return "json"; 41 | } 42 | 43 | @Override 44 | public String getDescription() { 45 | return "Json path (use pretty postprocessor first to get json from object)"; 46 | } 47 | 48 | @Override 49 | public String process(String result, List parameters) { 50 | if (parameters == null || parameters.isEmpty()) { 51 | LOGGER.debug("Cannot use [{}] post processor without any parameters", getName()); 52 | } else { 53 | if (parameters.size() != 1) { 54 | LOGGER.debug("[{}] post processor only need one parameter, rest will be ignored", getName()); 55 | } 56 | String path = parameters.get(0); 57 | try { 58 | JsonNode node = mapper.readTree(result).at(path); 59 | if (node.isMissingNode()) { 60 | return "No node found with json path expression: " + path; 61 | } else { 62 | if (node.isTextual()) { 63 | return node.asText(); 64 | } else { 65 | return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); 66 | } 67 | } 68 | } catch (IOException e) { 69 | LOGGER.warn("Unable to read tree", e); 70 | } catch (IllegalArgumentException e) { 71 | LOGGER.warn("Illegal argument: " + path, e); 72 | return e.getMessage(); 73 | } 74 | } 75 | return result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/PrettyJsonPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess.provided; 18 | 19 | import com.fasterxml.jackson.core.JsonProcessingException; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.github.fonimus.ssh.shell.postprocess.PostProcessor; 22 | import com.github.fonimus.ssh.shell.postprocess.PostProcessorException; 23 | import lombok.AllArgsConstructor; 24 | import lombok.extern.slf4j.Slf4j; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * Pretty json post processor 30 | */ 31 | @Slf4j 32 | @AllArgsConstructor 33 | public class PrettyJsonPostProcessor 34 | implements PostProcessor { 35 | 36 | private final ObjectMapper mapper; 37 | 38 | @Override 39 | public String getName() { 40 | return "pretty"; 41 | } 42 | 43 | @Override 44 | public String getDescription() { 45 | return "Pretty print thanks to json format"; 46 | } 47 | 48 | @Override 49 | public String process(Object result, List parameters) throws PostProcessorException { 50 | try { 51 | return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result); 52 | } catch (JsonProcessingException e) { 53 | LOGGER.warn("Unable to prettify object: {}", result); 54 | throw new PostProcessorException("Unable to prettify object. " + e.getMessage(), e); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/SavePostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess.provided; 18 | 19 | import com.github.fonimus.ssh.shell.postprocess.PostProcessor; 20 | import com.github.fonimus.ssh.shell.postprocess.PostProcessorException; 21 | import lombok.extern.slf4j.Slf4j; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.nio.charset.StandardCharsets; 26 | import java.nio.file.Files; 27 | import java.util.List; 28 | 29 | import static java.nio.file.StandardOpenOption.APPEND; 30 | import static java.nio.file.StandardOpenOption.CREATE; 31 | 32 | /** 33 | * Post processor used to save console result into file 34 | */ 35 | @Slf4j 36 | public class SavePostProcessor 37 | implements PostProcessor { 38 | 39 | public static final String SAVE = "save"; 40 | 41 | private static final String REPLACE_REGEX = "(\\x1b\\x5b|\\x9b)[\\x30-\\x3f]*[\\x20-\\x2f]*[\\x40-\\x7e]"; 42 | 43 | @Override 44 | public String getName() { 45 | return SAVE; 46 | } 47 | 48 | @Override 49 | public String getDescription() { 50 | return "Post processor to save result to file (or use special character '>')"; 51 | } 52 | 53 | @Override 54 | public String process(Object result, List parameters) throws PostProcessorException { 55 | if (parameters == null || parameters.isEmpty()) { 56 | throw new PostProcessorException("Cannot save without file path !"); 57 | } else { 58 | if (parameters.size() != 1) { 59 | LOGGER.debug("[{}] post processor only need one parameter, rest will be ignored", getName()); 60 | } 61 | String path = parameters.get(0); 62 | if (path == null || path.isEmpty()) { 63 | throw new PostProcessorException("Cannot save without file path !"); 64 | } 65 | File file = new File(path); 66 | try { 67 | String toWrite = string(result).replaceAll(REPLACE_REGEX, "") + "\n"; 68 | Files.writeString(file.toPath(), toWrite, CREATE, APPEND); 69 | return "Result saved to file: " + file.getAbsolutePath(); 70 | } catch (IOException e) { 71 | LOGGER.debug("Unable to write to file: " + file.getAbsolutePath(), e); 72 | throw new PostProcessorException("Unable to write to file: " + file.getAbsolutePath() + ". " + e.getMessage(), e); 73 | } 74 | } 75 | } 76 | 77 | private String string(Object result) { 78 | if (result instanceof String) { 79 | return (String) result; 80 | } else if (result instanceof Throwable) { 81 | return ((Throwable) result).getClass().getName() + ": " + ((Throwable) result).getMessage(); 82 | } else if (result != null) { 83 | return result.toString(); 84 | } else { 85 | return ""; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /starter/src/main/java/com/github/fonimus/ssh/shell/providers/ExtendedFileValueProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.providers; 18 | 19 | import com.github.fonimus.ssh.shell.ExtendedCompletionProposal; 20 | import org.springframework.shell.CompletionContext; 21 | import org.springframework.shell.CompletionProposal; 22 | import org.springframework.shell.standard.ValueProvider; 23 | import org.springframework.stereotype.Component; 24 | 25 | import java.io.File; 26 | import java.util.Arrays; 27 | import java.util.Collections; 28 | import java.util.List; 29 | import java.util.stream.Collectors; 30 | 31 | /** 32 | * Fixed file value provider (mostly for windows) and allow to not put space after proposal when directory 33 | */ 34 | @Component 35 | public class ExtendedFileValueProvider implements ValueProvider { 36 | 37 | @Override 38 | public List complete(CompletionContext completionContext) { 39 | String input = completionContext.currentWordUpToCursor(); 40 | int lastSlash = input.lastIndexOf("/"); 41 | File currentDir = lastSlash > -1 ? new File(input.substring(0, lastSlash + 1)) : new File("./"); 42 | String prefix = input.substring(lastSlash + 1); 43 | 44 | File[] files = currentDir.listFiles((dir, name) -> name.startsWith(prefix)); 45 | if (files == null || files.length == 0) { 46 | return Collections.emptyList(); 47 | } 48 | return Arrays.stream(files) 49 | .map(f -> new ExtendedCompletionProposal(path(f), f.isFile())) 50 | .collect(Collectors.toList()); 51 | } 52 | 53 | private static String path(File f) { 54 | String path = f.getPath().replaceAll("\\\\", "/"); 55 | return f.isDirectory() ? path + "/" : path; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | com.github.fonimus.ssh.shell.SshShellAutoConfiguration 2 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/AbstractShellHelperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.auth.SshAuthentication; 20 | import org.apache.sshd.server.Environment; 21 | import org.jline.reader.LineReader; 22 | import org.jline.terminal.Size; 23 | import org.jline.terminal.Terminal; 24 | import org.jline.utils.NonBlockingReader; 25 | import org.junit.jupiter.api.BeforeEach; 26 | 27 | import java.io.PrintWriter; 28 | import java.util.Collections; 29 | import java.util.List; 30 | 31 | import static com.github.fonimus.ssh.shell.SshShellUtilsTest.mockChannelSession; 32 | import static org.mockito.Mockito.mock; 33 | import static org.mockito.Mockito.when; 34 | 35 | public abstract class AbstractShellHelperTest { 36 | 37 | protected static SshShellHelper h; 38 | 39 | protected static LineReader lr; 40 | 41 | protected static Terminal ter; 42 | 43 | protected static PrintWriter writer; 44 | 45 | protected NonBlockingReader reader; 46 | 47 | @BeforeEach 48 | public void each() { 49 | h = new SshShellHelper(null); 50 | h.setDefaultTerminal(ter); 51 | h.setDefaultLineReader(lr); 52 | List auth = Collections.singletonList("ROLE_ACTUATOR"); 53 | lr = mock(LineReader.class); 54 | ter = mock(Terminal.class); 55 | writer = mock(PrintWriter.class); 56 | when(ter.writer()).thenReturn(writer); 57 | reader = mock(NonBlockingReader.class); 58 | when(ter.reader()).thenReturn(reader); 59 | when(lr.getTerminal()).thenReturn(ter); 60 | 61 | Environment sshEnv = mock(Environment.class); 62 | SshContext ctx = new SshContext(new SshShellRunnable(new SshShellProperties(), null, 63 | null, null, null, null, null, null, 64 | mockChannelSession(4L), sshEnv, null, null, null, null), ter, lr, 65 | new SshAuthentication("user", "user", null, null, auth)); 66 | SshShellCommandFactory.SSH_THREAD_CONTEXT.set(ctx); 67 | when(ter.getType()).thenReturn("osx"); 68 | when(ter.getSize()).thenReturn(new Size(123, 40)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/AbstractTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.auth.SshAuthentication; 20 | import com.github.fonimus.ssh.shell.commands.actuator.ActuatorCommand; 21 | import org.junit.jupiter.api.AfterEach; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint; 24 | import org.springframework.boot.actuate.beans.BeansEndpoint; 25 | import org.springframework.boot.actuate.context.ShutdownEndpoint; 26 | import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint; 27 | import org.springframework.boot.actuate.env.EnvironmentEndpoint; 28 | import org.springframework.boot.actuate.health.HealthEndpoint; 29 | import org.springframework.boot.actuate.info.InfoEndpoint; 30 | import org.springframework.boot.actuate.logging.LoggersEndpoint; 31 | import org.springframework.boot.actuate.management.ThreadDumpEndpoint; 32 | import org.springframework.boot.actuate.metrics.MetricsEndpoint; 33 | import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint; 34 | import org.springframework.boot.actuate.session.SessionsEndpoint; 35 | import org.springframework.boot.actuate.web.mappings.MappingsEndpoint; 36 | import org.springframework.context.ApplicationContext; 37 | import org.springframework.context.annotation.Lazy; 38 | import org.springframework.core.env.Environment; 39 | 40 | import java.util.Collections; 41 | 42 | public abstract class AbstractTest { 43 | 44 | @Autowired 45 | protected ApplicationContext context; 46 | 47 | @Autowired 48 | protected Environment environment; 49 | 50 | @Autowired 51 | protected SshShellProperties properties; 52 | 53 | @Autowired 54 | protected ActuatorCommand cmd; 55 | 56 | @Autowired 57 | protected BeansEndpoint beans; 58 | 59 | @Autowired 60 | protected ConditionsReportEndpoint conditions; 61 | 62 | @Autowired 63 | protected ConfigurationPropertiesReportEndpoint configprops; 64 | 65 | @Autowired 66 | protected EnvironmentEndpoint env; 67 | 68 | @Autowired 69 | protected HealthEndpoint health; 70 | 71 | @Autowired 72 | protected InfoEndpoint info; 73 | 74 | @Autowired 75 | protected LoggersEndpoint loggers; 76 | 77 | @Autowired 78 | protected MetricsEndpoint metrics; 79 | 80 | @Autowired 81 | protected MappingsEndpoint mappings; 82 | 83 | @Autowired 84 | protected ScheduledTasksEndpoint scheduledtasks; 85 | 86 | @Autowired 87 | protected SessionsEndpoint sessions; 88 | 89 | @Autowired 90 | @Lazy 91 | protected ShutdownEndpoint shutdown; 92 | 93 | @Autowired 94 | protected ThreadDumpEndpoint threaddump; 95 | 96 | protected void setRole(String role) { 97 | SshShellCommandFactory.SSH_THREAD_CONTEXT.set(new SshContext(new SshShellRunnable(properties, null, null, 98 | null, null, null, null, null, null, null, null, null, null, null), null, null, new SshAuthentication( 99 | "user", "user", null, null, Collections.singletonList(role)))); 100 | } 101 | 102 | protected void setActuatorRole() { 103 | setRole("ACTUATOR"); 104 | } 105 | 106 | @AfterEach 107 | protected void afterEach() { 108 | SshShellCommandFactory.SSH_THREAD_CONTEXT.remove(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/ExtendedCompleterAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.fonimus.ssh.shell; 2 | 3 | import org.jline.reader.Candidate; 4 | import org.jline.reader.impl.completer.ArgumentCompleter; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.shell.CompletionProposal; 8 | import org.springframework.shell.Shell; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.mockito.ArgumentMatchers.any; 16 | import static org.mockito.Mockito.mock; 17 | import static org.mockito.Mockito.when; 18 | 19 | class ExtendedCompleterAdapterTest { 20 | 21 | private ExtendedCompleterAdapter adapter; 22 | private Shell shell; 23 | 24 | @BeforeEach 25 | void setUp() { 26 | shell = mock(Shell.class); 27 | adapter = new ExtendedCompleterAdapter(shell); 28 | } 29 | 30 | @Test 31 | void complete() { 32 | when(shell.complete(any())).thenReturn(Arrays.asList( 33 | new CompletionProposal("co"), 34 | new ExtendedCompletionProposal("ecp", false) 35 | )); 36 | List list = new ArrayList<>(); 37 | 38 | adapter.complete(null, new ArgumentCompleter.ArgumentLine("test", 0), list); 39 | 40 | assertEquals(2, list.size()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/ExtendedInputTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.springframework.shell.Input; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 24 | 25 | class ExtendedInputTest { 26 | 27 | private final Input input = () -> "one, two three"; 28 | 29 | private final Input inputWithQuotes = () -> "one, 'two' three"; 30 | 31 | private final Input inputWithChars = () -> "one, two three | grep toto > /tmp/file"; 32 | 33 | @Test 34 | void input() { 35 | ExtendedInput i = new ExtendedInput(input); 36 | assertEquals(input.rawText(), i.rawText()); 37 | assertEquals(input.words(), i.words()); 38 | i = new ExtendedInput(inputWithQuotes); 39 | assertEquals(inputWithQuotes.rawText(), i.rawText()); 40 | assertEquals(inputWithQuotes.words(), i.words()); 41 | } 42 | 43 | @Test 44 | void inputWithChars() { 45 | ExtendedInput i = new ExtendedInput(inputWithChars); 46 | assertNotEquals(inputWithChars.rawText(), i.rawText()); 47 | assertEquals(input.rawText() + " ", i.rawText()); 48 | assertNotEquals(inputWithChars.words(), i.words()); 49 | assertEquals(input.words(), i.words()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/JavaConnect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.jcraft.jsch.Channel; 20 | import com.jcraft.jsch.JSch; 21 | import com.jcraft.jsch.Session; 22 | 23 | import java.io.PipedInputStream; 24 | import java.io.PipedOutputStream; 25 | import java.nio.charset.StandardCharsets; 26 | 27 | public class JavaConnect { 28 | 29 | public static void main(String[] args) { 30 | 31 | String host = "localhost"; 32 | String user = "user"; 33 | int port = 2222; 34 | String password = "password"; 35 | String command = "help"; 36 | 37 | PipedInputStream pis = new PipedInputStream(); 38 | PipedOutputStream pos = new PipedOutputStream(); 39 | 40 | try { 41 | java.util.Properties config = new java.util.Properties(); 42 | config.put("StrictHostKeyChecking", "no"); 43 | JSch jsch = new JSch(); 44 | Session session = jsch.getSession(user, host, port); 45 | session.setPassword(password); 46 | session.setConfig(config); 47 | session.connect(); 48 | System.out.println("> Connected"); 49 | 50 | Channel channel = session.openChannel("shell"); 51 | 52 | channel.setInputStream(new PipedInputStream(pos)); 53 | channel.setOutputStream(new PipedOutputStream(pis)); 54 | channel.connect(); 55 | 56 | System.out.println("> Typing command '" + command + "'"); 57 | pos.write((command + "\r").getBytes(StandardCharsets.UTF_8)); 58 | pos.flush(); 59 | 60 | byte[] tmp = new byte[1024]; 61 | // Note: this main will never stop unless ssh server is stopped 62 | while (true) { 63 | while (pis.available() > 0) { 64 | int i = pis.read(tmp, 0, 1024); 65 | if (i < 0) break; 66 | System.out.print(new String(tmp, 0, i)); 67 | } 68 | if (channel.isClosed()) { 69 | System.out.println("\n> exit-status: " + channel.getExitStatus()); 70 | break; 71 | } 72 | try { 73 | Thread.sleep(1000); 74 | } catch (Exception ee) { 75 | // nothing to do 76 | } 77 | } 78 | channel.disconnect(); 79 | session.disconnect(); 80 | System.out.println("> Done"); 81 | System.exit(0); 82 | } catch (Exception e) { 83 | e.printStackTrace(); 84 | System.exit(1); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshHelperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.jcraft.jsch.Channel; 20 | import com.jcraft.jsch.JSch; 21 | import com.jcraft.jsch.JSchException; 22 | import com.jcraft.jsch.Session; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import java.io.*; 27 | import java.nio.charset.StandardCharsets; 28 | import java.time.Duration; 29 | import java.util.Properties; 30 | 31 | import static org.awaitility.Awaitility.await; 32 | import static org.junit.jupiter.api.Assertions.fail; 33 | 34 | public class SshHelperTest { 35 | 36 | public static final Logger LOGGER = LoggerFactory.getLogger(SshHelperTest.class); 37 | 38 | public static void call(SshShellProperties properties, Executor executor) { 39 | call(properties.getUser(), properties.getPassword(), properties.getHost(), properties.getPort(), executor); 40 | } 41 | 42 | public static void call(String user, String pass, SshShellProperties properties, Executor executor) { 43 | call(user, pass, properties.getHost(), properties.getPort(), executor); 44 | } 45 | 46 | public static void call(String user, String pass, String host, int port, Executor executor) { 47 | try { 48 | JSch jsch = new JSch(); 49 | Session session = jsch.getSession(user, host, port); 50 | session.setPassword(pass); 51 | Properties config = new Properties(); 52 | config.put("StrictHostKeyChecking", "no"); 53 | session.setConfig(config); 54 | session.connect(); 55 | Channel channel = session.openChannel("shell"); 56 | try (PipedInputStream pis = new PipedInputStream(); 57 | PipedOutputStream pos = new PipedOutputStream()) { 58 | channel.setInputStream(new PipedInputStream(pos)); 59 | channel.setOutputStream(new PipedOutputStream(pis)); 60 | channel.connect(); 61 | executor.execute(pis, pos); 62 | } catch (Exception e) { 63 | fail(e.toString()); 64 | } finally { 65 | channel.disconnect(); 66 | session.disconnect(); 67 | } 68 | } catch (JSchException ex) { 69 | fail(ex.toString()); 70 | } 71 | } 72 | 73 | public static void verifyResponse(InputStream pis, String response) { 74 | StringBuilder sb = new StringBuilder(); 75 | try { 76 | Thread.sleep(1000); 77 | } catch (InterruptedException e) { 78 | fail("Got interrupted exception while waiting"); 79 | } 80 | try { 81 | await().atMost(Duration.ofSeconds(5)).until(() -> { 82 | while (true) { 83 | sb.append((char) pis.read()); 84 | String s = sb.toString(); 85 | if (s.contains(response)) { 86 | break; 87 | } 88 | } 89 | return true; 90 | }); 91 | } finally { 92 | LOGGER.info("--------------- received::start ---------------"); 93 | LOGGER.info(sb.toString()); 94 | LOGGER.info("--------------- received::end ---------------"); 95 | } 96 | } 97 | 98 | public static void write(OutputStream os, String... input) throws IOException { 99 | for (String s : input) { 100 | os.write((s + "\r").getBytes(StandardCharsets.UTF_8)); 101 | os.flush(); 102 | } 103 | } 104 | 105 | @FunctionalInterface 106 | public interface Executor { 107 | 108 | void execute(InputStream is, OutputStream os) throws Exception; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationCustomAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.conf.SshShellPasswordConfigurationTest; 20 | import org.junit.jupiter.api.Test; 21 | import org.springframework.boot.autoconfigure.SpringBootApplication; 22 | import org.springframework.boot.test.context.SpringBootTest; 23 | import org.springframework.test.annotation.DirtiesContext; 24 | 25 | import java.util.Map; 26 | 27 | import static com.github.fonimus.ssh.shell.SshHelperTest.*; 28 | 29 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, 30 | classes = {SshShellApplicationCustomAuthenticatorTest.class, SshShellPasswordConfigurationTest.class}, 31 | properties = { 32 | "ssh.shell.port=2349", 33 | "management.endpoints.web.exposure.include=*", 34 | "spring.shell.interactive.enabled=false" 35 | } 36 | ) 37 | @SpringBootApplication 38 | @DirtiesContext 39 | public class SshShellApplicationCustomAuthenticatorTest 40 | extends AbstractTest { 41 | 42 | @Test 43 | void testSshCallInfoCommand() { 44 | Map result = info.info(); 45 | call("user", "user", properties, (is, os) -> { 46 | write(os, "info"); 47 | verifyResponse(is, result.toString()); 48 | }); 49 | } 50 | 51 | @Test 52 | void testSshCallInfoCommandOtherUser() { 53 | Map result = info.info(); 54 | call("myself", "myself", properties, (is, os) -> { 55 | write(os, "info"); 56 | verifyResponse(is, result.toString()); 57 | }); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationExclusionsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.boot.test.context.SpringBootTest; 22 | import org.springframework.test.annotation.DirtiesContext; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = 28 | SshShellApplicationExclusionsTest.class, properties = { 29 | "ssh.shell.port=2344", 30 | "ssh.shell.commands.actuator.excludes[0]=info", 31 | "ssh.shell.commands.actuator.excludes[1]=beans", 32 | "ssh.shell.user=user", 33 | "ssh.shell.host=127.0.0.1", 34 | "ssh.shell.prompt.text=test>", 35 | "ssh.shell.prompt.color=red", 36 | "ssh.shell.hostKeyFile=target/test.tmp", 37 | "ssh.shell.enable=true", 38 | "management.endpoints.web.exposure.include=*", 39 | "spring.shell.interactive.enabled=false" 40 | }) 41 | @SpringBootApplication 42 | @DirtiesContext 43 | public class SshShellApplicationExclusionsTest 44 | extends AbstractTest { 45 | 46 | @Test 47 | void testCommandAvailability() { 48 | setActuatorRole(); 49 | 50 | assertFalse(cmd.infoAvailability().isAvailable()); 51 | assertFalse(cmd.beansAvailability().isAvailable()); 52 | assertTrue(cmd.configpropsAvailability().isAvailable()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationLazyInitTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.conf.SshShellSessionConfigurationTest; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.boot.test.context.SpringBootTest; 22 | import org.springframework.test.annotation.DirtiesContext; 23 | 24 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 25 | classes = {SshShellApplicationLazyInitTest.class, SshShellSessionConfigurationTest.class}, 26 | properties = { 27 | "ssh.shell.port=2347", 28 | "ssh.shell.password=pass", 29 | "ssh.shell.shared-history=false", 30 | "ssh.shell.commands.manage-sessions.enable=true", 31 | "management.endpoints.web.exposure.include=*", 32 | "spring.main.lazy-initialization=true", 33 | "spring.shell.interactive.enabled=false" 34 | } 35 | ) 36 | @SpringBootApplication 37 | @DirtiesContext 38 | public class SshShellApplicationLazyInitTest 39 | extends SshShellApplicationWebTest { 40 | 41 | // exactly same tests as extended test, 42 | // the aim is to test if application starts with spring.main.lazy-initialization=true 43 | } 44 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationSecurityTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.conf.SshShellSecurityConfigurationTest; 20 | import org.junit.jupiter.api.Test; 21 | import org.springframework.boot.autoconfigure.SpringBootApplication; 22 | import org.springframework.boot.test.context.SpringBootTest; 23 | import org.springframework.test.annotation.DirtiesContext; 24 | 25 | import java.util.Map; 26 | 27 | import static com.github.fonimus.ssh.shell.SshHelperTest.*; 28 | 29 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 30 | classes = {SshShellApplicationSecurityTest.class, SshShellSecurityConfigurationTest.class}, 31 | properties = { 32 | "ssh.shell.port=2346", 33 | "ssh.shell.password=pass", 34 | "ssh.shell.authentication=security", 35 | "management.endpoints.web.exposure.include=*", 36 | "spring.shell.interactive.enabled=false" 37 | } 38 | ) 39 | @SpringBootApplication 40 | @DirtiesContext 41 | public class SshShellApplicationSecurityTest 42 | extends AbstractTest { 43 | 44 | @Test 45 | void testSshCallInfoCommandAdmin() { 46 | Map result = info.info(); 47 | call("admin", "admin", properties, (is, os) -> { 48 | write(os, "info"); 49 | verifyResponse(is, result.toString()); 50 | }); 51 | } 52 | 53 | @Test 54 | void testSshCallInfoCommandUser() { 55 | call("user", "password", properties, (is, os) -> { 56 | write(os, "health"); 57 | verifyResponse(is, "forbidden for current user"); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.commands.DatasourceCommand; 20 | import com.github.fonimus.ssh.shell.commands.JmxCommand; 21 | import com.github.fonimus.ssh.shell.commands.TasksCommand; 22 | import com.github.fonimus.ssh.shell.conf.SshShellSessionConfigurationTest; 23 | import com.github.fonimus.ssh.shell.conf.TaskServiceTest; 24 | import org.junit.jupiter.api.Test; 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint; 27 | import org.springframework.boot.autoconfigure.SpringBootApplication; 28 | import org.springframework.boot.test.context.SpringBootTest; 29 | import org.springframework.context.ApplicationContext; 30 | import org.springframework.test.annotation.DirtiesContext; 31 | 32 | import static org.junit.jupiter.api.Assertions.*; 33 | 34 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, 35 | classes = {SshShellApplicationTest.class, SshShellSessionConfigurationTest.class}, 36 | properties = { 37 | "ssh.shell.port=2345", 38 | "ssh.shell.password=pass", 39 | "management.endpoints.web.exposure.include=*", 40 | "spring.shell.interactive.enabled=false", 41 | "spring.jmx.enabled=true" 42 | }) 43 | @SpringBootApplication 44 | @DirtiesContext 45 | public class SshShellApplicationTest 46 | extends AbstractCommandTest { 47 | 48 | @Autowired(required = false) 49 | protected JmxCommand jmx; 50 | 51 | @Autowired(required = false) 52 | protected DatasourceCommand ds; 53 | 54 | @Autowired(required = false) 55 | protected TasksCommand tasks; 56 | 57 | @Autowired(required = false) 58 | protected ApplicationContext context; 59 | 60 | @Autowired(required = false) 61 | protected ConditionsReportEndpoint conditionsReportEndpoint; 62 | 63 | @Test 64 | void testCommandAvailability() { 65 | setActuatorRole(); 66 | 67 | super.commonCommandAvailability(); 68 | 69 | assertFalse(cmd.httpExchangesAvailability().isAvailable()); 70 | } 71 | 72 | @Test 73 | void testDatasourceCommand() { 74 | assertNotNull(ds); 75 | assertNotNull(ds.datasourceList()); 76 | assertNotNull(ds.datasourceQuery(0, "select 1")); 77 | assertThrows(IllegalStateException.class, () -> ds.datasourceProperties(0, "test")); 78 | assertThrows(IllegalStateException.class, () -> ds.datasourceUpdate(0, "unknown")); 79 | } 80 | 81 | @Test 82 | void testJmxCommand() { 83 | assertNotNull(jmx); 84 | jmx.jmxList(null); 85 | jmx.jmxList("org.springframework.boot:type=Endpoint,name=Shutdown"); 86 | jmx.jmxList("unknown"); 87 | } 88 | 89 | @Test 90 | void testTasksCommand() { 91 | assertNotNull(tasks); 92 | String tasksListResult = tasks.tasksList(null, true); 93 | assertNotNull(tasksListResult); 94 | assertTrue(tasksListResult.contains(TaskServiceTest.class.getName() + ".test")); 95 | assertThrows(IllegalArgumentException.class, () -> tasks.tasksStop(false, "unknown")); 96 | assertThrows(IllegalArgumentException.class, () -> tasks.tasksRestart(false, "unknown")); 97 | assertThrows(IllegalArgumentException.class, () -> tasks.tasksSingle(false, "unknown")); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationWebTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import com.github.fonimus.ssh.shell.commands.ManageSessionsCommand; 20 | import com.github.fonimus.ssh.shell.conf.SshShellSessionConfigurationTest; 21 | import com.github.fonimus.ssh.shell.manage.SshShellSessionManager; 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.boot.autoconfigure.SpringBootApplication; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | import org.springframework.test.annotation.DirtiesContext; 26 | 27 | import static com.github.fonimus.ssh.shell.SshHelperTest.call; 28 | import static com.github.fonimus.ssh.shell.SshHelperTest.write; 29 | import static org.junit.jupiter.api.Assertions.*; 30 | 31 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 32 | classes = {SshShellApplicationWebTest.class, SshShellSessionConfigurationTest.class}, 33 | properties = { 34 | "ssh.shell.port=2346", 35 | "ssh.shell.password=pass", 36 | "ssh.shell.shared-history=false", 37 | "ssh.shell.commands.manage-sessions.enable=true", 38 | "management.endpoints.web.exposure.include=*", 39 | "spring.shell.interactive.enabled=false" 40 | } 41 | ) 42 | @SpringBootApplication 43 | @DirtiesContext 44 | public class SshShellApplicationWebTest 45 | extends AbstractCommandTest { 46 | 47 | @Test 48 | void testCommandAvailability() { 49 | setActuatorRole(); 50 | 51 | super.commonCommandAvailability(); 52 | 53 | assertTrue(cmd.sessionsAvailability().isAvailable()); 54 | } 55 | 56 | @Test 57 | void testCommandAvailabilityWithoutRole() { 58 | setRole("USER"); 59 | 60 | assertAll( 61 | () -> assertFalse(cmd.auditAvailability().isAvailable()), 62 | () -> assertFalse(cmd.beansAvailability().isAvailable()), 63 | () -> assertFalse(cmd.conditionsAvailability().isAvailable()), 64 | () -> assertFalse(cmd.configpropsAvailability().isAvailable()), 65 | () -> assertFalse(cmd.envAvailability().isAvailable()), 66 | () -> assertFalse(cmd.healthAvailability().isAvailable()), 67 | () -> assertTrue(cmd.infoAvailability().isAvailable()), 68 | () -> assertFalse(cmd.loggersAvailability().isAvailable()), 69 | () -> assertFalse(cmd.metricsAvailability().isAvailable()), 70 | () -> assertFalse(cmd.mappingsAvailability().isAvailable()), 71 | () -> assertFalse(cmd.scheduledtasksAvailability().isAvailable()), 72 | () -> assertFalse(cmd.shutdownAvailability().isAvailable()), 73 | () -> assertFalse(cmd.threaddumpAvailability().isAvailable()) 74 | ); 75 | } 76 | 77 | @Test 78 | void testManageSessions() { 79 | ManageSessionsCommand manageSessionsCommand = context.getBean(ManageSessionsCommand.class); 80 | SshShellSessionManager sshShellSessionManager = context.getBean(SshShellSessionManager.class); 81 | 82 | call("user", "pass", properties, (is, os) -> { 83 | write(os, "help"); 84 | Thread.sleep(1000); 85 | // to set 86 | setCtx(""); 87 | assertNotNull(manageSessionsCommand.manageSessionsList()); 88 | Long oneId = sshShellSessionManager.listSessions().keySet().iterator().next(); 89 | assertTrue(manageSessionsCommand.manageSessionsInfo(0L).contains("not found")); 90 | assertTrue(manageSessionsCommand.manageSessionsInfo(oneId).contains("/127.0.0.1")); 91 | assertTrue(manageSessionsCommand.manageSessionsStop(0L).contains("Unable to stop session")); 92 | assertTrue(manageSessionsCommand.manageSessionsStop(oneId).contains("stopped")); 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/SshShellUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell; 18 | 19 | import org.apache.sshd.common.channel.PtyMode; 20 | import org.apache.sshd.common.io.IoSession; 21 | import org.apache.sshd.server.channel.ChannelSession; 22 | import org.apache.sshd.server.session.ServerSession; 23 | import org.jline.terminal.Attributes; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | import static org.junit.jupiter.api.Assertions.assertFalse; 30 | import static org.mockito.Mockito.mock; 31 | import static org.mockito.Mockito.when; 32 | 33 | public class SshShellUtilsTest { 34 | 35 | @Test 36 | void testFillAttributes() { 37 | Attributes attributes = new Attributes(); 38 | Map map = new HashMap<>(); 39 | for (PtyMode value : PtyMode.values()) { 40 | map.put(value, 1); 41 | } 42 | SshShellUtils.fill(attributes, map); 43 | assertFalse(attributes.getControlChars().isEmpty()); 44 | } 45 | 46 | public static ChannelSession mockChannelSession(Long id) { 47 | ChannelSession session = mock(ChannelSession.class); 48 | ServerSession serverSession = mock(ServerSession.class); 49 | when(session.getSession()).thenReturn(serverSession); 50 | IoSession ioSession = mock(IoSession.class); 51 | when(serverSession.getIoSession()).thenReturn(ioSession); 52 | when(ioSession.getId()).thenReturn(id); 53 | return session; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/auth/SshShellPublicKeyAuthenticationProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.fonimus.ssh.shell.auth; 2 | 3 | import org.apache.sshd.common.io.IoSession; 4 | import org.apache.sshd.server.session.ServerSession; 5 | import org.junit.jupiter.api.BeforeAll; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.core.io.ClassPathResource; 8 | import org.springframework.core.io.FileSystemResource; 9 | 10 | import java.io.File; 11 | import java.nio.file.Files; 12 | import java.nio.file.Paths; 13 | import java.security.KeyFactory; 14 | import java.security.PublicKey; 15 | import java.security.spec.X509EncodedKeySpec; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.when; 20 | 21 | class SshShellPublicKeyAuthenticationProviderTest { 22 | 23 | private static PublicKey pub; 24 | private static PublicKey wrongPub; 25 | 26 | private SshShellPublicKeyAuthenticationProvider pubKeyAuthProv; 27 | 28 | @BeforeAll 29 | public static void init() throws Exception { 30 | KeyFactory kf = KeyFactory.getInstance("RSA"); 31 | pub = kf.generatePublic(new X509EncodedKeySpec(Files.readAllBytes(Paths.get("src/test/resources/.ssh/pub.der")))); 32 | wrongPub = kf.generatePublic(new X509EncodedKeySpec(Files.readAllBytes(Paths.get("src/test/resources/.ssh/wrong_pub.der")))); 33 | } 34 | 35 | @Test 36 | public void testFile() throws Exception { 37 | File file = new File("src/test/resources/.ssh/authorized.keys"); 38 | assertTrue(file.exists()); 39 | internalTest(file); 40 | } 41 | 42 | @Test 43 | public void testSpringFileResource() throws Exception { 44 | FileSystemResource resource = new FileSystemResource("src/test/resources/.ssh/authorized.keys"); 45 | assertTrue(resource.exists()); 46 | internalTest(resource.getFile()); 47 | } 48 | 49 | @Test 50 | public void testSpringClasspathResource() throws Exception { 51 | ClassPathResource resource = new ClassPathResource(".ssh/authorized.keys"); 52 | assertTrue(resource.exists()); 53 | internalTest(resource.getFile()); 54 | } 55 | 56 | @Test 57 | public void testNotExisting() throws Exception { 58 | pubKeyAuthProv = new SshShellPublicKeyAuthenticationProvider(new File("not-existing")); 59 | assertFalse(pubKeyAuthProv.exists()); 60 | assertEquals(-1, pubKeyAuthProv.size()); 61 | } 62 | 63 | private void internalTest(File file) throws Exception { 64 | pubKeyAuthProv = new SshShellPublicKeyAuthenticationProvider(file); 65 | assertTrue(pubKeyAuthProv.exists()); 66 | ServerSession session = mock(ServerSession.class); 67 | IoSession io = mock(IoSession.class); 68 | when(session.getIoSession()).thenReturn(io); 69 | assertTrue(pubKeyAuthProv.authenticate("user", pub, session)); 70 | assertFalse(pubKeyAuthProv.authenticate("user", wrongPub, session)); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/commands/HistoryCommandTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellHelper; 20 | import com.github.fonimus.ssh.shell.SshShellProperties; 21 | import org.jline.reader.History; 22 | import org.jline.reader.impl.history.DefaultHistory; 23 | import org.junit.jupiter.api.BeforeEach; 24 | import org.junit.jupiter.api.Test; 25 | import org.junit.jupiter.params.ParameterizedTest; 26 | import org.junit.jupiter.params.provider.ValueSource; 27 | 28 | import java.io.File; 29 | import java.io.IOException; 30 | import java.util.List; 31 | 32 | import static org.junit.jupiter.api.Assertions.*; 33 | import static org.mockito.Mockito.mock; 34 | import static org.mockito.Mockito.when; 35 | 36 | class HistoryCommandTest { 37 | 38 | private HistoryCommand shared; 39 | 40 | private HistoryCommand notShared; 41 | 42 | @BeforeEach 43 | void setUp() { 44 | SshShellHelper helper = mock(SshShellHelper.class); 45 | History history = new DefaultHistory(); 46 | history.add("cmd1"); 47 | history.add("cmd2"); 48 | when(helper.getHistory()).thenReturn(history); 49 | when(helper.isLocalPrompt()).thenReturn(false); 50 | shared = new HistoryCommand(new SshShellProperties(), helper); 51 | SshShellProperties properties = new SshShellProperties(); 52 | properties.setSharedHistory(false); 53 | notShared = new HistoryCommand(properties, helper); 54 | } 55 | 56 | @ParameterizedTest 57 | @ValueSource(booleans = {true, false}) 58 | void testGet(boolean displayArray) throws Exception { 59 | testGet(shared, displayArray); 60 | testGet(notShared, displayArray); 61 | } 62 | 63 | @SuppressWarnings("unchecked") 64 | private void testGet(HistoryCommand cmd, boolean displayArray) throws Exception { 65 | if (!displayArray) { 66 | String result = (String) cmd.history(null, false); 67 | assertEquals("cmd1\ncmd2\n", result); 68 | } else { 69 | List lines = (List) cmd.history(null, true); 70 | assertNotNull(lines); 71 | assertEquals(2, lines.size()); 72 | assertEquals("cmd1", lines.get(0)); 73 | assertEquals("cmd2", lines.get(1)); 74 | } 75 | } 76 | 77 | @Test 78 | void testWrite() throws Exception { 79 | testWrite(shared, "target/test-write-history.txt"); 80 | testWrite(notShared, "target/test-write-history-shared.txt"); 81 | } 82 | 83 | private void testWrite(HistoryCommand cmd, String fileName) throws IOException { 84 | File file = new File(fileName); 85 | String lines = (String) cmd.history(file, false); 86 | assertNotNull(lines); 87 | assertTrue(lines.startsWith("Wrote 2 entries to")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/commands/PostProcessorsCommandTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.github.fonimus.ssh.shell.SshShellHelper; 21 | import com.github.fonimus.ssh.shell.SshShellProperties; 22 | import com.github.fonimus.ssh.shell.postprocess.provided.GrepPostProcessor; 23 | import com.github.fonimus.ssh.shell.postprocess.provided.JsonPointerPostProcessor; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import java.util.Arrays; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | class PostProcessorsCommandTest { 31 | 32 | @Test 33 | void postprocessors() { 34 | GrepPostProcessor grep = new GrepPostProcessor(); 35 | JsonPointerPostProcessor json = new JsonPointerPostProcessor(new ObjectMapper()); 36 | String result = new PostProcessorsCommand(new SshShellHelper(null), 37 | new SshShellProperties(), Arrays.asList(grep, json)).postprocessors().toString(); 38 | 39 | assertTrue(result.startsWith("Available Post-Processors")); 40 | assertTrue(result.contains(grep.getName())); 41 | assertTrue(result.contains(json.getName())); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/commands/StacktraceCommandTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands; 18 | 19 | import com.github.fonimus.ssh.shell.SshShellHelper; 20 | import com.github.fonimus.ssh.shell.SshShellProperties; 21 | import com.github.fonimus.ssh.shell.postprocess.ExtendedResultHandlerService; 22 | import org.jline.terminal.Terminal; 23 | import org.junit.jupiter.api.Test; 24 | import org.mockito.Mockito; 25 | 26 | import java.io.PrintWriter; 27 | import java.io.StringWriter; 28 | 29 | class StacktraceCommandTest { 30 | 31 | @Test 32 | void stacktrace() { 33 | ExtendedResultHandlerService.THREAD_CONTEXT.set(null); 34 | 35 | StacktraceCommand cmd = new StacktraceCommand(new SshShellHelper(null), new SshShellProperties()); 36 | Terminal terminal = Mockito.mock(Terminal.class); 37 | Mockito.when(terminal.writer()).thenReturn(new PrintWriter(new StringWriter())); 38 | cmd.setTerminal(terminal); 39 | cmd.stacktrace(); 40 | Mockito.verify(terminal, Mockito.never()).writer(); 41 | 42 | ExtendedResultHandlerService.THREAD_CONTEXT.set(new IllegalArgumentException("[TEST]")); 43 | 44 | cmd.stacktrace(); 45 | Mockito.verify(terminal, Mockito.times(1)).writer(); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/commands/system/SystemCommandTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.commands.system; 18 | 19 | import com.github.fonimus.ssh.shell.AbstractShellHelperTest; 20 | import com.github.fonimus.ssh.shell.SshShellProperties; 21 | import org.jline.terminal.Size; 22 | import org.junit.jupiter.api.BeforeEach; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | import static org.mockito.Mockito.when; 27 | 28 | class SystemCommandTest extends AbstractShellHelperTest { 29 | 30 | private SystemCommand command; 31 | 32 | @BeforeEach 33 | void setUp() { 34 | command = new SystemCommand(h, new SshShellProperties()); 35 | } 36 | 37 | @Test 38 | void jvmEnv() { 39 | command.jvmEnv(false); 40 | command.jvmEnv(true); 41 | } 42 | 43 | @Test 44 | void jvmProperties() { 45 | command.jvmProperties(false); 46 | command.jvmProperties(true); 47 | } 48 | 49 | @Test 50 | void threads() throws Exception { 51 | for (SystemCommand.ThreadColumn tc : SystemCommand.ThreadColumn.values()) { 52 | assertNotNull(command.threads(SystemCommand.ThreadAction.list, tc, true, true, null)); 53 | } 54 | assertNotNull(command.threads(SystemCommand.ThreadAction.dump, SystemCommand.ThreadColumn.name, true, true, 55 | Thread.currentThread().getId())); 56 | assertThrows(IllegalArgumentException.class, 57 | () -> assertNotNull(command.threads(SystemCommand.ThreadAction.dump, 58 | SystemCommand.ThreadColumn.name, true, true, null))); 59 | assertThrows(IllegalArgumentException.class, 60 | () -> assertNotNull(command.threads(SystemCommand.ThreadAction.dump, 61 | SystemCommand.ThreadColumn.name, true, true, -1L))); 62 | 63 | when(reader.read(100L)).thenReturn(113); 64 | assertEquals("", command.threads(SystemCommand.ThreadAction.list, SystemCommand.ThreadColumn.name, true, false, 65 | null)); 66 | 67 | when(ter.getSize()).thenReturn(new Size(10, 10)); 68 | assertEquals("", command.threads(SystemCommand.ThreadAction.list, SystemCommand.ThreadColumn.name, true, false, 69 | null)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellPasswordConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.conf; 18 | 19 | import com.github.fonimus.ssh.shell.auth.SshShellAuthenticationProvider; 20 | import org.springframework.context.annotation.Bean; 21 | 22 | public class SshShellPasswordConfigurationTest { 23 | 24 | @Bean 25 | public SshShellAuthenticationProvider passwordAuthenticator() { 26 | return (user, pass, serverSession) -> user.equals(pass); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellSecurityConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.conf; 18 | 19 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.security.authentication.AuthenticationManager; 23 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 24 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 25 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 26 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 27 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 28 | import org.springframework.security.crypto.password.PasswordEncoder; 29 | import org.springframework.security.web.SecurityFilterChain; 30 | import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; 31 | 32 | @Configuration 33 | @EnableWebSecurity 34 | @EnableMethodSecurity 35 | @EnableJdbcHttpSession 36 | public class SshShellSecurityConfigurationTest { 37 | 38 | @Bean 39 | public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception { 40 | http.authorizeHttpRequests() 41 | .requestMatchers(EndpointRequest.to("info")).permitAll() 42 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") 43 | .and().authenticationManager(authManager); 44 | return http.build(); 45 | } 46 | 47 | @Bean 48 | public PasswordEncoder passwordEncoder() { 49 | return new BCryptPasswordEncoder(); 50 | } 51 | 52 | @Bean 53 | public AuthenticationManager authManager(HttpSecurity http) throws Exception { 54 | AuthenticationManagerBuilder authenticationManagerBuilder = 55 | http.getSharedObject(AuthenticationManagerBuilder.class); 56 | authenticationManagerBuilder.inMemoryAuthentication() 57 | .withUser("user") 58 | .password(passwordEncoder().encode("password")) 59 | .roles("USER") 60 | .and() 61 | .withUser("actuator") 62 | .password(passwordEncoder().encode("password")) 63 | .roles("ACTUATOR") 64 | .and() 65 | .withUser("admin") 66 | .password(passwordEncoder().encode("admin")) 67 | .roles("ADMIN", "ACTUATOR"); 68 | return authenticationManagerBuilder.build(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellSessionConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.conf; 18 | 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 21 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; 22 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 23 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 24 | import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; 25 | import org.springframework.transaction.PlatformTransactionManager; 26 | 27 | import javax.sql.DataSource; 28 | 29 | @EnableJdbcHttpSession 30 | public class SshShellSessionConfigurationTest { 31 | 32 | @Bean 33 | public EmbeddedDatabase dataSource() { 34 | return new EmbeddedDatabaseBuilder() 35 | .setType(EmbeddedDatabaseType.H2) 36 | .addScript("org/springframework/session/jdbc/schema-h2.sql").build(); 37 | } 38 | 39 | @Bean 40 | public PlatformTransactionManager transactionManager(DataSource dataSource) { 41 | return new DataSourceTransactionManager(dataSource); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/conf/TaskServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.fonimus.ssh.shell.conf; 2 | 3 | import org.springframework.scheduling.annotation.EnableScheduling; 4 | import org.springframework.scheduling.annotation.Scheduled; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | @EnableScheduling 9 | public class TaskServiceTest { 10 | 11 | @Scheduled(cron = "0 0 0 * * *") 12 | public void test() { 13 | // test 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/listeners/SshShellListenerServiceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.listeners; 18 | 19 | import org.apache.sshd.server.channel.ChannelSession; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.util.Arrays; 23 | 24 | import static com.github.fonimus.ssh.shell.SshShellUtilsTest.mockChannelSession; 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertNotNull; 27 | 28 | class SshShellListenerServiceTest { 29 | 30 | @Test 31 | void name() { 32 | SshShellListenerService service = new SshShellListenerService(Arrays.asList( 33 | event -> { 34 | assertNotNull(event); 35 | assertNotNull(event.toString()); 36 | assertNotNull(event.getType()); 37 | assertNotNull(event.getSession()); 38 | assertEquals(123L, event.getSessionId()); 39 | }, event -> { 40 | throw new IllegalArgumentException("[TEST]"); 41 | } 42 | )); 43 | 44 | ChannelSession session = mockChannelSession(123L); 45 | 46 | service.onSessionStarted(session); 47 | service.onSessionStopped(session); 48 | service.onSessionError(session); 49 | 50 | // no exception during executions event with illegal argument exception -> only error log 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/ExtendedResultHandlerServiceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import com.github.fonimus.ssh.shell.SshContext; 20 | import com.github.fonimus.ssh.shell.postprocess.provided.GrepPostProcessor; 21 | import com.github.fonimus.ssh.shell.postprocess.provided.SavePostProcessor; 22 | import org.junit.jupiter.api.BeforeEach; 23 | import org.junit.jupiter.api.Test; 24 | import org.mockito.ArgumentCaptor; 25 | import org.mockito.Mockito; 26 | import org.springframework.shell.ResultHandlerService; 27 | 28 | import java.util.Arrays; 29 | import java.util.Collections; 30 | 31 | import static com.github.fonimus.ssh.shell.SshShellCommandFactory.SSH_THREAD_CONTEXT; 32 | import static org.junit.jupiter.api.Assertions.assertEquals; 33 | import static org.junit.jupiter.api.Assertions.assertTrue; 34 | 35 | class ExtendedResultHandlerServiceTest { 36 | 37 | private ExtendedResultHandlerService rh; 38 | private ArgumentCaptor captor; 39 | 40 | @BeforeEach 41 | void setUp() { 42 | ResultHandlerService rhMock = Mockito.mock(ResultHandlerService.class); 43 | captor = ArgumentCaptor.forClass(Object.class); 44 | Mockito.doNothing().when(rhMock).handle(captor.capture()); 45 | rh = new ExtendedResultHandlerService(rhMock, 46 | Arrays.asList(new GrepPostProcessor(), new GrepPostProcessor(), new SavePostProcessor()) 47 | ); 48 | SSH_THREAD_CONTEXT.set(new SshContext(null, null, null, null)); 49 | } 50 | 51 | @Test 52 | void handleResultNull() { 53 | rh.handle(null); 54 | assertEquals(0, captor.getAllValues().size()); 55 | } 56 | 57 | @Test 58 | void handleResultThrowable() { 59 | IllegalArgumentException ex = new IllegalArgumentException("[TEST]"); 60 | rh.handle(ex); 61 | assertEquals(1, captor.getAllValues().size()); 62 | assertEquals(ex, captor.getAllValues().get(0)); 63 | } 64 | 65 | @Test 66 | void handleResultUnknownPostProcessor() { 67 | SSH_THREAD_CONTEXT.get().getPostProcessorsList().add( 68 | new PostProcessorObject("unknown" 69 | )); 70 | 71 | rh.handle("result"); 72 | assertEquals(2, captor.getAllValues().size()); 73 | assertTrue(((String) captor.getAllValues().get(0)).contains("Unknown post processor")); 74 | assertEquals("result", captor.getAllValues().get(1)); 75 | } 76 | 77 | @Test 78 | void handleResultWrongPostProcessorArgument() { 79 | SSH_THREAD_CONTEXT.get().getPostProcessorsList().add( 80 | new PostProcessorObject("grep" 81 | )); 82 | 83 | Object obj = new PostProcessorObject("test"); 84 | rh.handle(obj); 85 | assertEquals(2, captor.getAllValues().size()); 86 | assertTrue(((String) captor.getAllValues().get(0)).contains("can only apply to class")); 87 | assertEquals(obj, captor.getAllValues().get(1)); 88 | } 89 | 90 | @Test 91 | void handleResultPostProcessorError() { 92 | SSH_THREAD_CONTEXT.get().getPostProcessorsList().add( 93 | new PostProcessorObject("save") 94 | ); 95 | rh.handle("result"); 96 | assertEquals(1, captor.getAllValues().size()); 97 | assertTrue(((String) captor.getAllValues().get(0)).contains("Cannot save")); 98 | } 99 | 100 | @Test 101 | void handleResultNominal() { 102 | SSH_THREAD_CONTEXT.get().getPostProcessorsList().add( 103 | new PostProcessorObject("grep", Collections.singletonList("result")) 104 | ); 105 | rh.handle("result"); 106 | assertEquals(1, captor.getAllValues().size()); 107 | assertEquals("result", captor.getAllValues().get(0)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/GrepPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import com.github.fonimus.ssh.shell.postprocess.provided.GrepPostProcessor; 20 | import org.junit.jupiter.api.BeforeAll; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.Arrays; 24 | import java.util.Collections; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertAll; 27 | import static org.junit.jupiter.api.Assertions.assertEquals; 28 | 29 | class GrepPostProcessorTest { 30 | 31 | public static final String TEST = "test\ntoto\ntiti\ntest"; 32 | 33 | private static GrepPostProcessor processor; 34 | 35 | @BeforeAll 36 | static void init() { 37 | processor = new GrepPostProcessor(); 38 | } 39 | 40 | @Test 41 | void process() { 42 | assertAll("grep", 43 | () -> assertEquals(TEST, processor.process(TEST, null)), 44 | () -> assertEquals(TEST, processor.process(TEST, Collections.singletonList(""))), 45 | () -> assertEquals("test\ntest", processor.process(TEST, Collections.singletonList("test"))), 46 | () -> assertEquals("test\ntoto\ntest", processor.process(TEST, Arrays.asList("test", "toto"))), 47 | () -> assertEquals("toto", processor.process(TEST, Collections.singletonList("toto"))) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/HighlightPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import com.github.fonimus.ssh.shell.PromptColor; 20 | import com.github.fonimus.ssh.shell.SshShellHelper; 21 | import com.github.fonimus.ssh.shell.postprocess.provided.HighlightPostProcessor; 22 | import org.junit.jupiter.api.BeforeAll; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertAll; 29 | import static org.junit.jupiter.api.Assertions.assertEquals; 30 | 31 | class HighlightPostProcessorTest { 32 | 33 | public static final String TEST = "test\ntoto\ntiti\ntest"; 34 | 35 | private static HighlightPostProcessor processor; 36 | 37 | @BeforeAll 38 | static void init() { 39 | processor = new HighlightPostProcessor(); 40 | } 41 | 42 | @Test 43 | void process() { 44 | assertAll("highlight", 45 | () -> assertEquals(TEST, processor.process(TEST, null)), 46 | () -> assertEquals(TEST, processor.process(TEST, Collections.singletonList(""))), 47 | () -> assertEquals(TEST.replaceAll("test", SshShellHelper.getBackgroundColoredMessage("test", 48 | PromptColor.YELLOW)), processor.process(TEST, Collections.singletonList("test"))), 49 | () -> assertEquals(TEST.replaceAll("toto", SshShellHelper.getBackgroundColoredMessage("toto", 50 | PromptColor.YELLOW)), processor.process(TEST, Collections.singletonList("toto"))), 51 | () -> assertEquals(TEST.replaceAll("test", SshShellHelper.getBackgroundColoredMessage("test", 52 | PromptColor.YELLOW)).replaceAll("toto", SshShellHelper.getBackgroundColoredMessage("toto", 53 | PromptColor.YELLOW)), processor.process(TEST, Arrays.asList("test", "toto"))) 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/JsonPointerPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.github.fonimus.ssh.shell.postprocess.provided.JsonPointerPostProcessor; 21 | import org.junit.jupiter.api.BeforeAll; 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.boot.actuate.health.Health; 24 | 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertAll; 29 | import static org.junit.jupiter.api.Assertions.assertEquals; 30 | 31 | class JsonPointerPostProcessorTest { 32 | 33 | private static JsonPointerPostProcessor processor; 34 | 35 | @BeforeAll 36 | static void init() { 37 | processor = new JsonPointerPostProcessor(new ObjectMapper()); 38 | } 39 | 40 | @Test 41 | void process() throws Exception { 42 | Health health = Health.down() 43 | .withDetail("test", "value") 44 | .withDetail("map", Collections.singletonMap("key", "map-value")) 45 | .withDetail("list", Collections.singletonList("item")) 46 | .build(); 47 | String test = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(health); 48 | 49 | assertAll("json pointer", 50 | () -> assertEquals("not-a-json", processor.process("not-a-json", Collections.singletonList("/test"))), 51 | () -> assertEquals(test, processor.process(test, null)), 52 | () -> assertEquals(test, processor.process(test, Collections.singletonList(null))), 53 | () -> assertEquals(test, processor.process(test, Collections.singletonList(""))), 54 | () -> assertEquals("Invalid input: JSON Pointer expression must start with '/': \"test\"", 55 | processor.process(test, Collections.singletonList("test"))), 56 | () -> assertEquals("No node found with json path expression: /not-existing", 57 | processor.process(test, Collections.singletonList("/not-existing"))), 58 | () -> assertEqualsNoLineSeparator("{ \"test\" : \"value\", \"map\" : { \"key\" : \"map-value\" " + 59 | "}, \"list\" : [ \"item\" ]}", 60 | processor.process(test, Collections.singletonList("/details"))), 61 | () -> assertEquals("value", processor.process(test, Collections.singletonList("/details/test"))), 62 | () -> assertEquals("[ \"item\" ]", processor.process(test, Collections.singletonList("/details/list"))), 63 | () -> assertEquals("item", processor.process(test, Collections.singletonList("/details/list/0"))), 64 | () -> assertEquals("No node found with json path expression: /details/list/1", 65 | processor.process(test, Collections.singletonList("/details/list/1"))), 66 | () -> assertEqualsNoLineSeparator("{ \"key\" : \"map-value\"}", processor.process(test, 67 | Collections.singletonList("/details/map"))), 68 | () -> assertEquals("map-value", processor.process(test, Collections.singletonList("/details/map/key"))), 69 | () -> assertEquals("map-value", processor.process(test, Arrays.asList("/details/map/key", 70 | "dont-care"))), 71 | () -> assertEquals("No node found with json path expression: /details/map/not-a-key", 72 | processor.process(test, Collections.singletonList("/details/map/not-a-key"))) 73 | ); 74 | 75 | } 76 | 77 | private static void assertEqualsNoLineSeparator(String expected, String actual) { 78 | assertEquals(clean(expected), clean(actual)); 79 | } 80 | 81 | private static String clean(String toClean) { 82 | return toClean == null ? null : toClean.replaceAll("[\\r\\n]+", ""); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/PrettyJsonPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.github.fonimus.ssh.shell.postprocess.provided.PrettyJsonPostProcessor; 21 | import org.junit.jupiter.api.BeforeAll; 22 | import org.junit.jupiter.api.Test; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | import org.springframework.boot.actuate.health.Health; 26 | 27 | import java.util.Collections; 28 | 29 | import static org.junit.jupiter.api.Assertions.assertEquals; 30 | import static org.junit.jupiter.api.Assertions.assertThrows; 31 | 32 | class PrettyJsonPostProcessorTest { 33 | 34 | private static PrettyJsonPostProcessor processor; 35 | 36 | @BeforeAll 37 | static void init() { 38 | processor = new PrettyJsonPostProcessor(new ObjectMapper()); 39 | } 40 | 41 | @Test 42 | void process() throws Exception { 43 | String test = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString( 44 | Health.down() 45 | .withDetail("test", "value") 46 | .withDetail("map", Collections.singletonMap("key", "map-value")) 47 | .withDetail("list", Collections.singletonList("item")) 48 | .build()); 49 | 50 | assertEquals("\"test\"", processor.process("test", null)); 51 | assertEquals(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(test), 52 | processor.process(test, null)); 53 | assertThrows(PostProcessorException.class, () -> processor.process(new NotSerializableObject("test"), null)); 54 | } 55 | 56 | public static class NotSerializableObject { 57 | 58 | public static final Logger LOGGER = LoggerFactory.getLogger(NotSerializableObject.class); 59 | 60 | public NotSerializableObject(String test) { 61 | LOGGER.info(test); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/SavePostProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.postprocess; 18 | 19 | import com.github.fonimus.ssh.shell.postprocess.provided.SavePostProcessor; 20 | import org.junit.jupiter.api.BeforeAll; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.io.File; 24 | import java.nio.file.Files; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertThrows; 29 | import static org.junit.jupiter.api.Assertions.assertTrue; 30 | 31 | class SavePostProcessorTest { 32 | 33 | public static final String TEST = "to-write"; 34 | 35 | private static SavePostProcessor processor; 36 | 37 | @BeforeAll 38 | static void init() { 39 | processor = new SavePostProcessor(); 40 | } 41 | 42 | @Test 43 | void process() throws Exception { 44 | File file = new File("target/test.txt"); 45 | if (file.exists()) { 46 | assertTrue(Files.deleteIfExists(file.toPath())); 47 | } 48 | assertTrue( 49 | assertThrows(PostProcessorException.class, () -> processor.process(TEST, null)).getMessage().startsWith("Cannot save without file path !")); 50 | assertTrue(assertThrows(PostProcessorException.class, () -> processor.process(TEST, 51 | Collections.singletonList(""))).getMessage() 52 | .startsWith("Cannot save without file path !")); 53 | assertTrue(assertThrows(PostProcessorException.class, () -> processor.process(TEST, 54 | Collections.singletonList(null))).getMessage() 55 | .startsWith("Cannot save without file path !")); 56 | assertTrue( 57 | assertThrows(PostProcessorException.class, () -> processor.process(TEST, Collections.singletonList( 58 | "target/not-existing-dir/file.txt"))) 59 | .getMessage().startsWith("Unable to write to file:")); 60 | assertTrue(processor.process(TEST, Arrays.asList("target/test.txt", "other param ignored")).startsWith( 61 | "Result saved to file:")); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /starter/src/test/java/com/github/fonimus/ssh/shell/providers/ExtendedFileValueProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 François Onimus 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.fonimus.ssh.shell.providers; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.springframework.shell.CompletionContext; 21 | import org.springframework.shell.CompletionProposal; 22 | 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 28 | 29 | class ExtendedFileValueProviderTest { 30 | 31 | @Test 32 | void complete() { 33 | ExtendedFileValueProvider provider = new ExtendedFileValueProvider(); 34 | List result = provider.complete( 35 | new CompletionContext(Arrays.asList("--file", "src"), 1, 3, null, null)); 36 | assertNotEquals(0, result.size()); 37 | result = provider.complete( 38 | new CompletionContext(Arrays.asList("--file", "xxx"), 1, 3, null, null)); 39 | assertEquals(0, result.size()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /starter/src/test/resources/.ssh/authorized.keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDlkW3hZVrrOPjcswznq5WDbF8IV3iEZo4EP2A9Ofl2dqqfKhgObDQuMUQN1OebBpUCo9W13CUle1ca7AdXJY4ZU62+YJCDKy9+hSz+KWfl7kJx+StL57sv3ProC3n7gAH+uedY6pSlHr6zTjsiEyGZbaKaSShFBX0ta3j+vZ11T+bxyK3j7BFJ2YcUa4ys+gphm3by/LV6xEhr3tohnFhfO5COKrEYqYC97EjsgDhcaFtpzjOnFoYC+HDCAFCwKWJj+Puu9qNj+NlT0gX4DuVFbWEEwH2ZB+qhk5/b75pAGLikRTK9lPLrTX0MX283lAWrD84d6xKWZaqrcxoP1HBnTCaqs5XGrXN81UP2EK+3KYNcNoIwN9x5UABOc1vDfK91IyYNgheVl8NT+T7TYNNj27piXFlvSgDx5dmuqBopYri+IqS17KJXaVgRjbdAwkWHNCqqSW9RoGDxUWlYgOh7KhNlN2m4AcW9YMEd1z2WWaYQpevYG3p3GGEZ8Av5Hbk= francois@MacBook-Pro-de-Francois.local 2 | -------------------------------------------------------------------------------- /starter/src/test/resources/.ssh/pub.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fonimus/ssh-shell-spring-boot/5c7fc58b87bfdef8c46a81cec49ce08ce728c2e5/starter/src/test/resources/.ssh/pub.der -------------------------------------------------------------------------------- /starter/src/test/resources/.ssh/wrong_pub.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fonimus/ssh-shell-spring-boot/5c7fc58b87bfdef8c46a81cec49ce08ce728c2e5/starter/src/test/resources/.ssh/wrong_pub.der -------------------------------------------------------------------------------- /starter/src/test/resources/script.txt: -------------------------------------------------------------------------------- 1 | wait 500 2 | wait 500 3 | wait 500 4 | wait 500 5 | wait 500 6 | wait 500 7 | wait 500 8 | wait 500 9 | wait 500 10 | wait 500 11 | wait 500 12 | wait 500 13 | wait 500 14 | wait 500 15 | wait 700 16 | wait 700 17 | wait 500 18 | wait 500 19 | wait 500 20 | wait 500 21 | wait 700 22 | wait 700 23 | wait 500 24 | wait 700 25 | wait 700 26 | wait 500 27 | wait 500 28 | wait 500 29 | wait 500 30 | wait 500 31 | wait 500 32 | xxx 33 | yyy 34 | --------------------------------------------------------------------------------