├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ ├── Feature_request.md │ ├── Question.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── codeql │ └── codeql-config.yml ├── stale.yml └── workflows │ └── continuous-integration.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── CHANGES.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── commitlint.config.js ├── docs.json ├── index.js ├── intl ├── cs │ └── messages.json ├── de │ └── messages.json ├── en │ └── messages.json ├── es │ └── messages.json ├── fr │ └── messages.json ├── it │ └── messages.json ├── ja │ └── messages.json ├── ko │ └── messages.json ├── nl │ └── messages.json ├── pl │ └── messages.json ├── pt │ └── messages.json ├── ru │ └── messages.json ├── tr │ └── messages.json ├── zh-Hans │ └── messages.json └── zh-Hant │ └── messages.json ├── lib ├── http.js └── soap-connector.js ├── package-lock.json ├── package.json ├── renovate.json └── test ├── fixtures ├── test-certificate.p12 └── test-certificate2.pfx ├── sample-req.json ├── sample-req.xml ├── sample-res.json ├── sample-res.xml ├── security.test.js ├── test.js └── wsdls ├── calculator_external.wsdl ├── stockquote.wsdl └── weather.wsdl /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | 6 | --- 7 | 8 | 18 | 19 | ## Steps to reproduce 20 | 21 | 22 | 23 | ## Current Behavior 24 | 25 | 26 | 27 | ## Expected Behavior 28 | 29 | 30 | 31 | ## Link to reproduction sandbox 32 | 33 | 37 | 38 | ## Additional information 39 | 40 | 45 | 46 | ## Related Issues 47 | 48 | 49 | 50 | _See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_ 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: feature 5 | 6 | --- 7 | 8 | ## Suggestion 9 | 10 | 11 | 12 | ## Use Cases 13 | 14 | 18 | 19 | ## Examples 20 | 21 | 22 | 23 | ## Acceptance criteria 24 | 25 | TBD - will be filled by the team. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help. 4 | labels: question 5 | 6 | --- 7 | 8 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Report a security vulnerability 4 | url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues 5 | about: Do not report security vulnerabilities using GitHub issues. Please send an email to `security@loopback.io` instead. 6 | - name: Get help on StackOverflow 7 | url: https://stackoverflow.com/tags/loopbackjs 8 | about: Please ask and answer questions on StackOverflow. 9 | - name: Join our mailing list 10 | url: https://groups.google.com/forum/#!forum/loopbackjs 11 | about: You can also post your question to our mailing list. 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## Checklist 12 | 13 | - [ ] DCO (Developer Certificate of Origin) [signed in all commits](https://loopback.io/doc/en/contrib/code-contrib.html) 14 | - [ ] `npm test` passes on your machine 15 | - [ ] New tests added or existing tests modified to cover all changes 16 | - [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html) 17 | - [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html) 18 | -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | paths-ignore: 2 | - test 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - critical 10 | - p1 11 | - major 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | # Comment to post when marking an issue as stale. Set to `false` to disable 15 | markComment: > 16 | This issue has been automatically marked as stale because it has not had 17 | recent activity. It will be closed if no further activity occurs. Thank you 18 | for your contributions. 19 | # Comment to post when closing a stale issue. Set to `false` to disable 20 | closeComment: > 21 | This issue has been closed due to continued inactivity. Thank you for your understanding. 22 | If you believe this to be in error, please contact one of the code owners, 23 | listed in the `CODEOWNERS` file at the top-level of this repository. 24 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | schedule: 11 | - cron: '0 2 * * 1' # At 02:00 on Monday 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | strategy: 17 | matrix: 18 | node-version: [18, 20] 19 | os: [ubuntu-latest] 20 | include: 21 | - os: macos-latest 22 | node-version: 18 23 | fail-fast: false 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - name: Git checkout 27 | uses: actions/checkout@v3 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | - name: Update NPM 36 | run: npm install -g npm 37 | - name: Bootstrap project 38 | run: npm ci --ignore-scripts 39 | - name: Run Tests 40 | run: npx --ignore-scripts nyc --reporter=lcov npm test --ignore-scripts 41 | - name: Coveralls Parallel 42 | uses: coverallsapp/github-action@master 43 | with: 44 | github-token: ${{ secrets.github_token }} 45 | flag-name: run-${{ matrix.os }}-node@${{ matrix.node-version }} 46 | parallel: true 47 | 48 | posttest: 49 | name: Post-Test 50 | needs: test 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Coveralls Finish 54 | uses: coverallsapp/github-action@master 55 | with: 56 | github-token: ${{ secrets.github_token }} 57 | parallel-finished: true 58 | 59 | commit-lint: 60 | name: Commit Lint 61 | runs-on: ubuntu-latest 62 | if: ${{ github.event.pull_request }} 63 | steps: 64 | - uses: actions/checkout@v3 65 | with: 66 | fetch-depth: 0 67 | - name: Use Node.js 18 68 | uses: actions/setup-node@v4 69 | with: 70 | node-version: 18 71 | - name: Bootstrap project 72 | run: npm ci --ignore-scripts 73 | - name: Verify commit linting 74 | run: npx --no-install commitlint --from origin/master --to HEAD --verbose 75 | 76 | codeql: 77 | name: CodeQL 78 | runs-on: ubuntu-latest 79 | steps: 80 | - uses: actions/checkout@v3 81 | - name: Initialize CodeQL 82 | uses: github/codeql-action/init@v3 83 | with: 84 | languages: 'javascript' 85 | config-file: ./.github/codeql/codeql-config.yml 86 | - name: Perform CodeQL Analysis 87 | uses: github/codeql-action/analyze@v3 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .idea 3 | node_modules 4 | npm-debug.log 5 | .vscode 6 | 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .project 2 | .idea 3 | node_modules 4 | npm-debug.log 5 | .vscode 6 | 7 | test 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | scripts-prepend-node-path=true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | - ppc64le 4 | - s390x 5 | branches: 6 | only: 7 | - master 8 | language: node_js 9 | node_js: 10 | - '10' 11 | - '12' 12 | - '14' 13 | - '16' 14 | before_install: npm install -g npm 15 | script: npm test --ignore-scripts 16 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2025-06-09, Version 8.0.19 2 | ========================== 3 | 4 | * fix(deps): update dependency strong-soap to v4.1.13 (#238) (renovate[bot]) 5 | 6 | * chore(deps): update dependency mocha to v11.6.0 (#237) (renovate[bot]) 7 | 8 | * chore(deps): update dependency mocha to v11.5.0 (#236) (renovate[bot]) 9 | 10 | * chore(deps): update dependency mocha to v11.4.0 (#235) (renovate[bot]) 11 | 12 | * fix(deps): update dependency debug to v4.4.1 (#234) (renovate[bot]) 13 | 14 | 15 | 2025-05-12, Version 8.0.18 16 | ========================== 17 | 18 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.8 (#233) (renovate[bot]) 19 | 20 | * fix(deps): update dependency strong-soap to v4.1.12 (#232) (renovate[bot]) 21 | 22 | * chore(deps): update commitlint monorepo to v19.8.1 (#231) (renovate[bot]) 23 | 24 | * chore(deps): update dependency mocha to v11.2.2 (#230) (renovate[bot]) 25 | 26 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.7 (#229) (renovate[bot]) 27 | 28 | 29 | 2025-04-14, Version 8.0.17 30 | ========================== 31 | 32 | * fix(deps): update dependency strong-soap to v4.1.11 (#228) (renovate[bot]) 33 | 34 | * chore: replace request (#227) (Diana Lau) 35 | 36 | 37 | 2025-03-18, Version 8.0.16 38 | ========================== 39 | 40 | * fix(deps): update dependency strong-soap to v4.1.10 (#226) (renovate[bot]) 41 | 42 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.6 (#225) (renovate[bot]) 43 | 44 | * chore(deps): update commitlint monorepo to v19.8.0 (#224) (renovate[bot]) 45 | 46 | 47 | 2025-02-12, Version 8.0.15 48 | ========================== 49 | 50 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.5 (#223) (renovate[bot]) 51 | 52 | * fix(deps): update dependency strong-soap to v4.1.9 (#222) (renovate[bot]) 53 | 54 | * chore(deps): update commitlint monorepo to v19.7.1 (#221) (renovate[bot]) 55 | 56 | * chore(deps): update dependency mocha to v11.1.0 (#220) (renovate[bot]) 57 | 58 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.4 (#219) (renovate[bot]) 59 | 60 | 61 | 2025-01-13, Version 8.0.14 62 | ========================== 63 | 64 | * fix(deps): update dependency strong-soap to v4.1.8 (#218) (renovate[bot]) 65 | 66 | * chore(deps): update dependency @commitlint/cli to v19.6.1 (#217) (renovate[bot]) 67 | 68 | * fix(deps): update dependency debug to v4.4.0 (#216) (renovate[bot]) 69 | 70 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.3 (#215) (renovate[bot]) 71 | 72 | 73 | 2024-12-05, Version 8.0.13 74 | ========================== 75 | 76 | * fix(deps): update dependency strong-soap to v4.1.7 (#214) (renovate[bot]) 77 | 78 | * chore(deps): update dependency mocha to v11 (#213) (renovate[bot]) 79 | 80 | * chore(deps): update commitlint monorepo to v19.6.0 (renovate[bot]) 81 | 82 | 83 | 2024-11-11, Version 8.0.12 84 | ========================== 85 | 86 | * fix(deps): update dependency strong-soap to v4.1.6 (#211) (renovate[bot]) 87 | 88 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.2 (#210) (renovate[bot]) 89 | 90 | * chore(deps): update dependency mocha to v10.8.2 (#209) (renovate[bot]) 91 | 92 | 93 | 2024-10-11, Version 8.0.11 94 | ========================== 95 | 96 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.1 (#207) (renovate[bot]) 97 | 98 | * fix(deps): update dependency strong-soap to v4.1.5 (#208) (renovate[bot]) 99 | 100 | * chore(deps): update dependency nyc to v17.1.0 (#206) (renovate[bot]) 101 | 102 | * chore(deps): update commitlint monorepo to v19.5.0 (#205) (renovate[bot]) 103 | 104 | * chore(deps): update dependency loopback-datasource-juggler to v5.1.0 (#204) (renovate[bot]) 105 | 106 | 107 | 2024-09-09, Version 8.0.10 108 | ========================== 109 | 110 | * fix(deps): update dependency strong-soap to v4.1.4 (#203) (renovate[bot]) 111 | 112 | * fix(deps): update dependency debug to v4.3.7 (#202) (renovate[bot]) 113 | 114 | * chore(deps): update commitlint monorepo to v19.4.1 (#201) (renovate[bot]) 115 | 116 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.12 (#200) (renovate[bot]) 117 | 118 | 119 | 2024-08-12, Version 8.0.9 120 | ========================= 121 | 122 | * fix(deps): update dependency strong-soap to v4.1.3 (#199) (renovate[bot]) 123 | 124 | * chore(deps): update dependency @commitlint/cli to v19.4.0 (#197) (renovate[bot]) 125 | 126 | * chore(deps): update dependency mocha to v10.7.3 (#198) (renovate[bot]) 127 | 128 | * fix(deps): update dependency debug to v4.3.6 (#196) (renovate[bot]) 129 | 130 | * chore(deps): update dependency mocha to v10.7.0 (#195) (renovate[bot]) 131 | 132 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.11 (#194) (renovate[bot]) 133 | 134 | 135 | 2024-07-04, Version 8.0.8 136 | ========================= 137 | 138 | * fix(deps): update dependency strong-soap to v4.1.2 (#193) (renovate[bot]) 139 | 140 | * chore(deps): update dependency mocha to v10.6.0 (#192) (renovate[bot]) 141 | 142 | * chore(deps): update dependency mocha to v10.5.2 (#191) (renovate[bot]) 143 | 144 | * fix(deps): update dependency strong-soap to v4.1.0 (#188) (renovate[bot]) 145 | 146 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.10 (#189) (renovate[bot]) 147 | 148 | * chore(deps): update dependency nyc to v17 (#190) (renovate[bot]) 149 | 150 | 151 | 2024-06-06, Version 8.0.7 152 | ========================= 153 | 154 | * fix(deps): update dependency debug to v4.3.5 (#187) (renovate[bot]) 155 | 156 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.9 (#186) (renovate[bot]) 157 | 158 | 159 | 2024-05-13, Version 8.0.6 160 | ========================= 161 | 162 | * fix(deps): update dependency strong-soap to v4.0.6 (#185) (renovate[bot]) 163 | 164 | * chore(deps): update dependency @commitlint/cli to v19.3.0 (#184) (renovate[bot]) 165 | 166 | * chore(deps): update commitlint monorepo to v19.2.2 (#183) (renovate[bot]) 167 | 168 | 169 | 2024-04-08, Version 8.0.5 170 | ========================= 171 | 172 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.8 (#182) (renovate[bot]) 173 | 174 | * fix(deps): update dependency strong-soap to v4.0.5 (#181) (renovate[bot]) 175 | 176 | * chore(deps): update dependency mocha to v10.4.0 (#180) (renovate[bot]) 177 | 178 | * fix(deps): update dependency strong-soap to v4.0.4 (#179) (renovate[bot]) 179 | 180 | * chore(deps): update commitlint monorepo to v19.2.1 (#178) (renovate[bot]) 181 | 182 | * fix(deps): update dependency strong-soap to v4.0.3 (#177) (renovate[bot]) 183 | 184 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.7 (#176) (renovate[bot]) 185 | 186 | 187 | 2024-03-06, Version 8.0.4 188 | ========================= 189 | 190 | * chore(deps): update commitlint monorepo to v19.0.3 (#175) (renovate[bot]) 191 | 192 | * chore(deps): update dependency @commitlint/cli to v19.0.1 (renovate[bot]) 193 | 194 | * fix(deps): update dependency strong-soap to v4.0.2 (renovate[bot]) 195 | 196 | * chore(deps): update commitlint monorepo to v19 (renovate[bot]) 197 | 198 | * chore(deps): update commitlint monorepo to v18.6.1 (#171) (renovate[bot]) 199 | 200 | 201 | 2024-02-12, Version 8.0.3 202 | ========================= 203 | 204 | * fix(deps): update dependency strong-soap to v4.0.1 (#170) (renovate[bot]) 205 | 206 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.6 (#169) (renovate[bot]) 207 | 208 | * chore(deps): update dependency mocha to v10.3.0 (#168) (renovate[bot]) 209 | 210 | * chore(deps): update commitlint monorepo to v18.6.0 (#167) (renovate[bot]) 211 | 212 | * fix(deps): update dependency strong-soap to v4 (#166) (renovate[bot]) 213 | 214 | 215 | 2024-01-09, Version 8.0.2 216 | ========================= 217 | 218 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.5 (#165) (renovate[bot]) 219 | 220 | * chore(deps): update github/codeql-action action to v3 (#163) (renovate[bot]) 221 | 222 | * chore(deps): update commitlint monorepo to v18.4.4 (#164) (renovate[bot]) 223 | 224 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.4 (#162) (renovate[bot]) 225 | 226 | 227 | 2023-12-07, Version 8.0.1 228 | ========================= 229 | 230 | * chore(deps): update commitlint monorepo to v18.4.3 (#161) (renovate[bot]) 231 | 232 | 233 | 2023-11-20, Version 8.0.0 234 | ========================= 235 | 236 | * chore(deps): update commitlint monorepo to v18.4.2 (#156) (renovate[bot]) 237 | 238 | * chore(deps): update commitlint monorepo to v18 (#158) (renovate[bot]) 239 | 240 | * chore(deps): update dependency loopback-datasource-juggler to v5.0.3 (#160) (renovate[bot]) 241 | 242 | * chore(deps): update dependency loopback-datasource-juggler to v5 (#155) (renovate[bot]) 243 | 244 | * fix(deps): update dependency strong-soap to v3.5.6 (#154) (renovate[bot]) 245 | 246 | * chore(deps): update actions/setup-node action to v4 (#159) (renovate[bot]) 247 | 248 | * chore: drop support for Node.js 16 and lower (#157) (Diana Lau) 249 | 250 | 251 | 2023-09-11, Version 7.0.4 252 | ========================= 253 | 254 | * fix(deps): update dependency strong-globalize to v6.0.6 (#152) (renovate[bot]) 255 | 256 | * chore(deps): update dependency loopback-datasource-juggler to v4.28.9 (#151) (renovate[bot]) 257 | 258 | 259 | 2023-08-14, Version 7.0.3 260 | ========================= 261 | 262 | * fix(deps): update dependency strong-soap to v3.5.4 (#150) (renovate[bot]) 263 | 264 | * chore(deps): update dependency @commitlint/cli to v17.7.1 (#149) (renovate[bot]) 265 | 266 | * chore(deps): update commitlint monorepo to v17.7.0 (#148) (renovate[bot]) 267 | 268 | * chore(deps): update commitlint monorepo to v17.6.7 (#147) (renovate[bot]) 269 | 270 | 271 | 2023-07-16, Version 7.0.2 272 | ========================= 273 | 274 | * fix(deps): update dependency strong-soap to v3.5.3 (#146) (renovate[bot]) 275 | 276 | * chore(deps): update dependency loopback-datasource-juggler to v4.28.8 (#145) (renovate[bot]) 277 | 278 | * chore(deps): update dependency loopback-datasource-juggler to v4.28.7 (#144) (renovate[bot]) 279 | 280 | * chore(deps): update commitlint monorepo to v17.6.6 (#143) (renovate[bot]) 281 | 282 | 283 | 2023-06-14, Version 7.0.1 284 | ========================= 285 | 286 | * chore(deps): update dependency loopback-datasource-juggler to v4.28.6 (#141) (renovate[bot]) 287 | 288 | * fix(deps): update dependency strong-soap to v3.5.2 (#142) (renovate[bot]) 289 | 290 | * fix: fix broken link in CI badge (#140) (Diana Lau) 291 | 292 | * chore(deps): update commitlint monorepo to v17.6.5 (#139) (renovate[bot]) 293 | 294 | * fix(deps): update dependency strong-soap to v3.5.1 (#138) (renovate[bot]) 295 | 296 | 297 | 2023-05-15, Version 7.0.0 298 | ========================= 299 | 300 | * chore(deps): update dependency mocha to v10.2.0 (#127) (renovate[bot]) 301 | 302 | * chore(deps): update actions/checkout action to v3 (#130) (renovate[bot]) 303 | 304 | * chore(deps): update commitlint monorepo to v17 (#132) (renovate[bot]) 305 | 306 | * chore(deps): update dependency loopback-datasource-juggler to v4.28.5 (#137) (renovate[bot]) 307 | 308 | * chore(deps): update commitlint monorepo to v13.2.1 (#126) (renovate[bot]) 309 | 310 | * chore(deps): update actions/setup-node action to v3 (#131) (renovate[bot]) 311 | 312 | * chore: remove loopback dependencies in tests (#135) (Diana Lau) 313 | 314 | * chore: drop support for Node.js 14 and lower (#134) (Diana Lau) 315 | 316 | 317 | 2023-03-29, Version 6.0.2 318 | ========================= 319 | 320 | * chore(deps): update github/codeql-action action to v2 (#133) (renovate[bot]) 321 | 322 | * chore(deps): add renovate.json (#119) (renovate[bot]) 323 | 324 | * chore: update package-lock (#125) (Diana Lau) 325 | 326 | 327 | 2022-11-14, Version 6.0.1 328 | ========================= 329 | 330 | * chore: update dep (#124) (Diana Lau) 331 | 332 | * docs: add SECURITY.md (#123) (Diana Lau) 333 | 334 | * docs: update coc (#122) (Diana Lau) 335 | 336 | * docs: add code of conduct (#121) (Diana Lau) 337 | 338 | * ci: update pipelines (Rifa Achrinza) 339 | 340 | * refactor: move repo to loopbackio org (#118) (Diana Lau) 341 | 342 | * ci: convert from travis to gh action (Agnes Lin) 343 | 344 | 345 | 2020-11-18, Version 6.0.0 346 | ========================= 347 | 348 | * feat: update deps and drop Node 8 support (Raymond Feng) 349 | 350 | 351 | 2020-10-14, Version 5.2.0 352 | ========================= 353 | 354 | * add feature to pass httpHeaders to client (Thiago Zenaro) 355 | 356 | * chore: switch to DCO (#114) (Diana Lau) 357 | 358 | * chore: add Node.js 14 to CI (#113) (Diana Lau) 359 | 360 | * chore: update CODEOWNERS (#112) (Diana Lau) 361 | 362 | * chore: update strong-globalize version (#111) (Diana Lau) 363 | 364 | 365 | 2020-02-10, Version 5.1.0 366 | ========================= 367 | 368 | * Update README.md (#107) (Realetive) 369 | 370 | * chore: improve issue and PR templates (Nora) 371 | 372 | * add node 12 to CI (Nora) 373 | 374 | * drop support for node 6 (Nora) 375 | 376 | * Update README.md (Iliya Zubakin) 377 | 378 | 379 | 2018-09-28, Version 5.0.0 380 | ========================= 381 | 382 | * chore(lib): add object destructuring (Francois) 383 | 384 | * feat(lib): use let/const and template string (Francois) 385 | 386 | * feat(package): update debug dependency to v4 (Francois) 387 | 388 | 389 | 2018-08-07, Version 4.3.0 390 | ========================= 391 | 392 | * Update to MIT license (#100) (Diana Lau) 393 | 394 | * feat: Add ability to pass json soapHeader (Mario Estrada) 395 | 396 | 397 | 2018-07-09, Version 4.2.0 398 | ========================= 399 | 400 | * chore: update node version and dependencies (virkt25) 401 | 402 | * Changed obsolete wsdl url and adapted tests (Francois) 403 | 404 | * [WebFM] cs/pl/ru translation (candytangnb) 405 | 406 | 407 | 2018-05-25, Version 4.1.0 408 | ========================= 409 | 410 | * Add test case for operation returning promise (shimks) 411 | 412 | 413 | 2017-10-12, Version 4.0.1 414 | ========================= 415 | 416 | * ClientSSL: use correct configuration variable (Kevin Delisle) 417 | 418 | * Fix CI by adding .travis.yml (#87) (Diana Lau) 419 | 420 | * Add stalebot configuration (Kevin Delisle) 421 | 422 | * Create Issue and PR Templates (#83) (Sakib Hasan) 423 | 424 | * Add CODEOWNER file (Diana Lau) 425 | 426 | * Update README.md (Rand McKinney) 427 | 428 | * Fix table formatting (crandmck) 429 | 430 | * Cleanup (crandmck) 431 | 432 | * Use periodictable instead of weather example (crandmck) 433 | 434 | 435 | 2017-02-07, Version 4.0.0 436 | ========================= 437 | 438 | * Upgrade mocha version (Raymond Feng) 439 | 440 | * switch test cases to use stockquote (rashmihunt) 441 | 442 | * fix typos (rashmihunt) 443 | 444 | * skip 3 tests which use ext weather service (rashmihunt) 445 | 446 | * Update README.md (Rand McKinney) 447 | 448 | * Update paid support URL (Siddhi Pai) 449 | 450 | * Start 3.x + drop support for Node v0.10/v0.12 (siddhipai) 451 | 452 | * Drop support for Node v0.10 and v0.12 (Siddhi Pai) 453 | 454 | * Start the development of the next major version (Siddhi Pai) 455 | 456 | * Updated README (crandmck) 457 | 458 | 459 | 2016-11-08, Version 3.0.1 460 | ========================= 461 | 462 | * removed intln entries (rashmihunt) 463 | 464 | 465 | 2016-10-06, Version 3.0.0 466 | ========================= 467 | 468 | * changed mocha timeout (rashmihunt) 469 | 470 | * changed debug string (rashmihunt) 471 | 472 | * fixed tests and debug string (rashmihunt) 473 | 474 | * integrate loopback connector with strong-soap (deepakrkris) 475 | 476 | * Update translation files - round#2 (Candy) 477 | 478 | * Add translated files (gunjpan) 479 | 480 | * Update deps to loopback 3.0.0 RC (Miroslav Bajtoš) 481 | 482 | * Remove juggler as a dependency (Amir Jafarian) 483 | 484 | * Use juggler@3 and loopback@3 for testing (Amir Jafarian) 485 | 486 | * Update globalization (Amir Jafarian) 487 | 488 | * Add support for globalization (Amir Jafarian) 489 | 490 | * Update URLs in CONTRIBUTING.md (#37) (Ryan Graham) 491 | 492 | 493 | 2016-06-23, Version 2.5.0 494 | ========================= 495 | 496 | * Upgrade to soap@0.16 (Raymond Feng) 497 | 498 | * Honor security options (Raymond Feng) 499 | 500 | * Update deps (Raymond Feng) 501 | 502 | * Update soap-connector.js (Ritchie Martori) 503 | 504 | 505 | 2016-05-06, Version 2.4.1 506 | ========================= 507 | 508 | * update copyright notices and license (Ryan Graham) 509 | 510 | 511 | 2016-03-04, Version 2.4.0 512 | ========================= 513 | 514 | * Upgrade deps and remove license check (Raymond Feng) 515 | 516 | 517 | 2016-02-19, Version 2.3.1 518 | ========================= 519 | 520 | * Remove sl-blip from dependencies (Miroslav Bajtoš) 521 | 522 | * Add new example location to README.md (Simon Ho) 523 | 524 | * Remove example dir (Simon Ho) 525 | 526 | * Fix typo in readme.md (David Cheung) 527 | 528 | * Changing soap invocation test to weather services (David Cheung) 529 | 530 | * Refer to licenses with a link (Sam Roberts) 531 | 532 | 533 | 2015-10-23, Version 2.3.0 534 | ========================= 535 | 536 | * Update soap dep (Raymond Feng) 537 | 538 | 539 | 2015-10-09, Version 2.2.0 540 | ========================= 541 | 542 | * Add support for request options (Raymond Feng) 543 | 544 | 545 | 2015-09-29, Version 2.1.1 546 | ========================= 547 | 548 | * Upgrade to soap@0.9.4 (Raymond Feng) 549 | 550 | 551 | 2015-09-29, Version 2.1.0 552 | ========================= 553 | 554 | * Use strongloop conventions for licensing (Sam Roberts) 555 | 556 | * Add connector hook to the example (Raymond Feng) 557 | 558 | 559 | 2015-06-01, Version 2.0.0 560 | ========================= 561 | 562 | 563 | 564 | 2015-06-01, Version 1.3.0 565 | ========================= 566 | 567 | * Update to latest node-soap (Raymond Feng) 568 | 569 | * Add hooks for the underlying soap invocation (Raymond Feng) 570 | 571 | * bump(soap) bump node-soap version from 0.8 to 0.9 (Alexandre L) 572 | 573 | * Add strongloop license check (Raymond Feng) 574 | 575 | 576 | 2015-02-24, Version 1.2.0 577 | ========================= 578 | 579 | * Update deps (Raymond Feng) 580 | 581 | * Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham) 582 | 583 | * Typo (Mike Valstar) 584 | 585 | * Update README.md (Mike Valstar) 586 | 587 | 588 | 2014-11-27, Version 1.1.3 589 | ========================= 590 | 591 | 592 | 593 | 2014-11-10, Version 1.1.2 594 | ========================= 595 | 596 | * Bump version (Raymond Feng) 597 | 598 | 599 | 2014-10-15, Version 1.1.1 600 | ========================= 601 | 602 | * Bump version (Raymond Feng) 603 | 604 | * Update deps (Raymond Feng) 605 | 606 | * Add type (Raymond Feng) 607 | 608 | * Add contribution guidelines (Ryan Graham) 609 | 610 | * fix for operations mapping (howard) 611 | 612 | 613 | 2014-07-09, Version 1.1.0 614 | ========================= 615 | 616 | * Remove peer dep on loopback-datasource-juggler (Miroslav Bajtoš) 617 | 618 | 619 | 2014-07-07, Version 1.0.1 620 | ========================= 621 | 622 | * Update deps (Raymond Feng) 623 | 624 | * Set up the DAO to avoid CRUD methods being remoted (Raymond Feng) 625 | 626 | * Allow username/password to be set at root level (Raymond Feng) 627 | 628 | * Add tests for XML/JSON conversions (Raymond Feng) 629 | 630 | * Update link to doc (Rand McKinney) 631 | 632 | * Add methods to convert between json and xml per operation (Raymond Feng) 633 | 634 | * Use a local wsdl (Raymond Feng) 635 | 636 | * Add link to wiki docs. (Rand McKinney) 637 | 638 | * Add docs.json for API docs (crandmck) 639 | 640 | 641 | 2014-04-14, Version 1.0.0 642 | ========================= 643 | 644 | * First release! 645 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners, 3 | # the last matching pattern has the most precendence. 4 | 5 | # Alumni members 6 | # @rashmihunt @deepakrkris @jannyHou 7 | 8 | # Core team members from IBM 9 | * @raymondfeng 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | LoopBack, as member project of the OpenJS Foundation, use 4 | [Contributor Covenant v2.0](https://contributor-covenant.org/version/2/0/code_of_conduct) 5 | as their code of conduct. The full text is included 6 | [below](#contributor-covenant-code-of-conduct-v2.0) in English, and translations 7 | are available from the Contributor Covenant organisation: 8 | 9 | - [contributor-covenant.org/translations](https://www.contributor-covenant.org/translations) 10 | - [github.com/ContributorCovenant](https://github.com/ContributorCovenant/contributor_covenant/tree/release/content/version/2/0) 11 | 12 | Refer to the sections on reporting and escalation in this document for the 13 | specific emails that can be used to report and escalate issues. 14 | 15 | ## Reporting 16 | 17 | ### Project Spaces 18 | 19 | For reporting issues in spaces related to LoopBack, please use the email 20 | `tsc@loopback.io`. The LoopBack Technical Steering Committee (TSC) handles CoC issues related to the spaces that it 21 | maintains. The project TSC commits to: 22 | 23 | - maintain the confidentiality with regard to the reporter of an incident 24 | - to participate in the path for escalation as outlined in the section on 25 | Escalation when required. 26 | 27 | ### Foundation Spaces 28 | 29 | For reporting issues in spaces managed by the OpenJS Foundation, for example, 30 | repositories within the OpenJS organization, use the email 31 | `report@lists.openjsf.org`. The Cross Project Council (CPC) is responsible for 32 | managing these reports and commits to: 33 | 34 | - maintain the confidentiality with regard to the reporter of an incident 35 | - to participate in the path for escalation as outlined in the section on 36 | Escalation when required. 37 | 38 | ## Escalation 39 | 40 | The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a 41 | foundation-wide team established to manage escalation when a reporter believes 42 | that a report to a member project or the CPC has not been properly handled. In 43 | order to escalate to the CoCP send an email to 44 | `coc-escalation@lists.openjsf.org`. 45 | 46 | For more information, refer to the full 47 | [Code of Conduct governance document](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/CODE_OF_CONDUCT.md). 48 | 49 | --- 50 | 51 | ## Contributor Covenant Code of Conduct v2.0 52 | 53 | ## Our Pledge 54 | 55 | We as members, contributors, and leaders pledge to make participation in our 56 | community a harassment-free experience for everyone, regardless of age, body 57 | size, visible or invisible disability, ethnicity, sex characteristics, gender 58 | identity and expression, level of experience, education, socio-economic status, 59 | nationality, personal appearance, race, religion, or sexual identity and 60 | orientation. 61 | 62 | We pledge to act and interact in ways that contribute to an open, welcoming, 63 | diverse, inclusive, and healthy community. 64 | 65 | ## Our Standards 66 | 67 | Examples of behavior that contributes to a positive environment for our 68 | community include: 69 | 70 | - Demonstrating empathy and kindness toward other people 71 | - Being respectful of differing opinions, viewpoints, and experiences 72 | - Giving and gracefully accepting constructive feedback 73 | - Accepting responsibility and apologizing to those affected by our mistakes, 74 | and learning from the experience 75 | - Focusing on what is best not just for us as individuals, but for the overall 76 | community 77 | 78 | Examples of unacceptable behavior include: 79 | 80 | - The use of sexualized language or imagery, and sexual attention or advances of 81 | any kind 82 | - Trolling, insulting or derogatory comments, and personal or political attacks 83 | - Public or private harassment 84 | - Publishing others' private information, such as a physical or email address, 85 | without their explicit permission 86 | - Other conduct which could reasonably be considered inappropriate in a 87 | professional setting 88 | 89 | ## Enforcement Responsibilities 90 | 91 | Community leaders are responsible for clarifying and enforcing our standards of 92 | acceptable behavior and will take appropriate and fair corrective action in 93 | response to any behavior that they deem inappropriate, threatening, offensive, 94 | or harmful. 95 | 96 | Community leaders have the right and responsibility to remove, edit, or reject 97 | comments, commits, code, wiki edits, issues, and other contributions that are 98 | not aligned to this Code of Conduct, and will communicate reasons for moderation 99 | decisions when appropriate. 100 | 101 | ## Scope 102 | 103 | This Code of Conduct applies within all community spaces, and also applies when 104 | an individual is officially representing the community in public spaces. 105 | Examples of representing our community include using an official e-mail address, 106 | posting via an official social media account, or acting as an appointed 107 | representative at an online or offline event. 108 | 109 | ## Enforcement 110 | 111 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 112 | reported to the community leaders responsible for enforcement at 113 | [tsc@loopback.io](mailto:tsc@loopback.io). All complaints will be reviewed and 114 | investigated promptly and fairly. 115 | 116 | All community leaders are obligated to respect the privacy and security of the 117 | reporter of any incident. 118 | 119 | ## Enforcement Guidelines 120 | 121 | Community leaders will follow these Community Impact Guidelines in determining 122 | the consequences for any action they deem in violation of this Code of Conduct: 123 | 124 | ### 1. Correction 125 | 126 | **Community Impact**: Use of inappropriate language or other behavior deemed 127 | unprofessional or unwelcome in the community. 128 | 129 | **Consequence**: A private, written warning from community leaders, providing 130 | clarity around the nature of the violation and an explanation of why the 131 | behavior was inappropriate. A public apology may be requested. 132 | 133 | ### 2. Warning 134 | 135 | **Community Impact**: A violation through a single incident or series of 136 | actions. 137 | 138 | **Consequence**: A warning with consequences for continued behavior. No 139 | interaction with the people involved, including unsolicited interaction with 140 | those enforcing the Code of Conduct, for a specified period of time. This 141 | includes avoiding interactions in community spaces as well as external channels 142 | like social media. Violating these terms may lead to a temporary or permanent 143 | ban. 144 | 145 | ### 3. Temporary Ban 146 | 147 | **Community Impact**: A serious violation of community standards, including 148 | sustained inappropriate behavior. 149 | 150 | **Consequence**: A temporary ban from any sort of interaction or public 151 | communication with the community for a specified period of time. No public or 152 | private interaction with the people involved, including unsolicited interaction 153 | with those enforcing the Code of Conduct, is allowed during this period. 154 | Violating these terms may lead to a permanent ban. 155 | 156 | ### 4. Permanent Ban 157 | 158 | **Community Impact**: Demonstrating a pattern of violation of community 159 | standards, including sustained inappropriate behavior, harassment of an 160 | individual, or aggression toward or disparagement of classes of individuals. 161 | 162 | **Consequence**: A permanent ban from any sort of public interaction within the 163 | community. 164 | 165 | ## Attribution 166 | 167 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 168 | version 2.0, available at 169 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 170 | 171 | Community Impact Guidelines were inspired by 172 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 173 | 174 | [homepage]: https://www.contributor-covenant.org 175 | 176 | For answers to common questions about this code of conduct, see the FAQ at 177 | https://www.contributor-covenant.org/faq. Translations are available at 178 | https://www.contributor-covenant.org/translations. 179 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `loopback-connector-soap`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `loopback-connector-soap` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Developer Certificate of Origin](#developer-certificate-of-origin) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Developer Certificate of Origin 23 | 24 | This project uses [DCO](https://developercertificate.org/). Be sure to sign off 25 | your commits using the `-s` flag or adding `Signed-off-By: Name` in the 26 | commit message. 27 | 28 | **Example** 29 | 30 | ``` 31 | git commit -s -m "feat: my commit message" 32 | ``` 33 | 34 | Also see the [Contributing to LoopBack](https://loopback.io/doc/en/contrib/code-contrib.html) to get you started. 35 | 36 | 37 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 38 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2012,2018. All Rights Reserved. 2 | Node module: loopback-connector-soap 3 | This project is licensed under the MIT License, full text below. 4 | 5 | -------- 6 | 7 | MIT license 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback-connector-soap 2 | 3 | [![CI](https://github.com/loopbackio/loopback-connector-soap/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/loopbackio/loopback-connector-soap/actions/workflows/continuous-integration.yml) 4 | [![Build Status](https://app.travis-ci.com/loopbackio/loopback-connector-soap.svg?branch=master)](https://app.travis-ci.com/loopbackio/loopback-connector-soap) 5 | [![Coverage Status](https://coveralls.io/repos/github/loopbackio/loopback-connector-soap/badge.svg?branch=master)](https://coveralls.io/github/loopbackio/loopback-connector-soap?branch=master) 6 | 7 | The SOAP connector enables LoopBack applications to interact with 8 | [SOAP](http://www.w3.org/TR/soap)-based web services described using 9 | [WSDL](http://www.w3.org/TR/wsdl). 10 | 11 |

12 | For more information, see the 13 | LoopBack documentation. 14 |

15 | 16 | ## Installation 17 | 18 | In your application root directory, enter: 19 | 20 | ```shell 21 | $ npm install loopback-connector-soap --save 22 | ``` 23 | 24 | This will install the module from npm and add it as a dependency to the application's  25 | [package.json](http://loopback.io/doc/en/lb2/package.json.html) file. 26 | 27 | ## Overview 28 | 29 | There are two ways to use the SOAP connector: 30 | 31 | - Use the LoopBack CLI `lb soap` command to automatically create a set of models based on a SOAP service WSDL file. Often, this will be the easiest way to connect to a SOAP web service, but may not be suitable for all applications. For more information, see [SOAP generator](http://loopback.io/doc/en/lb3/SOAP-generator.html). 32 | - Write the code manually, calling the `loopback-connector-soap` and data source APIs directly. **This is the approach illustrated here**. 33 | 34 | While both approaches use the `loopback-connector-soap` data source connector, they appear quite different. 35 | 36 | ## SOAP data source properties 37 | 38 | The following table describes the SOAP data source properties you can set in `datasources.json`. 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 107 | 110 | 111 | 112 | 113 | 114 | 122 | 123 | 124 | 125 | 126 | 133 | 134 | 135 |
PropertyTypeDescription
urlStringURL to the SOAP web service endpoint. If not present, defaults to the 53 | location attribute of the SOAP address for the service/port 54 | from the WSDL document; for example below it is http://www.webservicex.net/periodictable.asmx: 55 |
 56 | <wsdl:service name="periodictable">
 57 | <wsdl:port name="periodictableSoap" binding="tns:periodictableSoap">
 58 | <soap:address location="http://www.webservicex.net/periodictable.asmx"/>
 59 | </wsdl:port>
 60 | </wsdl:service>
61 |
wsdlStringHTTP URL or local file system path to the WSDL file. Default is ?wsdl. 67 | In the example above, it would be http://www.webservicex.net/periodictable.asmx?wsdl. 68 |
wsdl_optionsObjectIndicates additonal options to pass to the SOAP connector, for example allowing self signed certificates. 74 | For example: 75 |
wsdl_options: {
 76 |   rejectUnauthorized: false,
 77 |   strictSSL: false,
 78 |   requestCert: true,
 79 | }
wsdl_headersObjectIndicates additonal headers to pass to the SOAP connector, for example for sending http authorizations header. 85 | For example: 86 |
wsdl_headers: {
 87 |   Authorization: "Basic UGVyc29uYWwgYWNjb3VudDpORVdsazIwMTVAKSEl"
 88 | }
remotingEnabledBooleanIndicates whether the operations are exposed as REST APIs. To expose or hide a specific method, override with: 94 |
<Model>.<method>.shared = true | false;
95 |
operationsObjectMaps WSDL binding operations to Node.js methods. Each key in the JSON 101 | object becomes the name of a method on the model. 102 | See operations property below.
securityObjectsecurity configuration. 108 | See security property below. 109 |
soapHeadersArray of objects.Custom SOAP headers. An array of header properties. 115 | For example: 116 |
soapHeaders: [{
117 | element: {myHeader: 'XYZ'}, // The XML element in JSON object format
118 |   prefix: 'p1', // The XML namespace prefix for the header
119 |   namespace: 'http://ns1' // The XML namespace URI for the header
120 | }]
121 |
httpHeadersObjectCustom HTTP headers. An object of header properties. 127 | For example: 128 |
httpHeaders: {
129 |   "custom-header": "value of custom-header",
130 |   "custom-header-2": "value of custom-header-2"
131 | }
132 |
136 | 137 | ### operations property 138 | 139 | The `operations` property value is a JSON object that has a property (key) for each 140 | method being defined for the model. The corresponding value is an object with the 141 | following properties: 142 | 143 | | Property | Type | Description | 144 | |---|---|---| 145 | | service | String | WSDL service name | 146 | | port | String | WSDL port name | 147 | | operation | String | WSDL operation name | 148 | 149 | Here is an example operations property for the periodic table service: 150 | 151 | ```javascript 152 | operations: { 153 | // The key is the method name 154 | periodicTable: { 155 | service: 'periodictable', // The WSDL service name 156 | port: 'periodictableSoap', // The WSDL port name 157 | operation: 'GetAtomicNumber' // The WSDL operation name 158 | } 159 | } 160 | ``` 161 | 162 | **IMPORTANT**: When using the CLI data source generator, you must supply the "stringified JSON" value for this property. 163 | For example: 164 | 165 | ``` 166 | {"getAtomicWeight":{"service":"periodictable","port":"periodictableSoap","operation":"GetAtomicWeight"},"getAtomicNumber":{"service":"periodictable","port":"periodictableSoap","operation":"GetAtomicNumber"}} 167 | ``` 168 | 169 | To generate the stringified value, you can use the following code (for example): 170 | 171 | ``` 172 | var operations = { 173 | "operations": { 174 | "getAtomicWeight": { 175 | "service": "periodictable", 176 | "port": "periodictableSoap", 177 | "operation": "GetAtomicWeight" 178 | }, 179 | "getAtomicNumber": { 180 | "service": "periodictable", 181 | "port": "periodictableSoap", 182 | "operation": "GetAtomicNumber" 183 | } 184 | } 185 | }; 186 | 187 | var stringifiedOps = JSON.stringify (operations); 188 | console.log(stringifiedOps); 189 | ``` 190 | 191 | ### security property 192 | 193 | The `security` property value is a JSON object with a `scheme` property. 194 | The other properties of the object depend on the value of `scheme`. For example: 195 | 196 | ```javascript 197 | security: { 198 | scheme: 'WS', 199 | username: 'test', 200 | password: 'testpass', 201 | passwordType: 'PasswordDigest' 202 | } 203 | ``` 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 223 | 224 | 225 | 226 | 227 | 228 | 234 | 235 | 236 | 237 | 238 | 239 | 245 | 246 | 247 | 248 |
SchemeDescriptionOther properties
WSWSSecurity scheme 217 |
    218 |
  • username: the user name
  • 219 |
  • password: the password
  • 220 |
  • passwordType: default is 'PasswordText'
  • 221 |
222 |
BasicAuth1Basic auth scheme 229 |
    230 |
  • username: the user name
  • 231 |
  • password: the password
  • 232 |
233 |
ClientSSLClientSSL scheme 240 |
    241 |
  • keyPath: path to the private key file
  • 242 |
  • certPath: path to the certificate file
  • 243 |
244 |
249 | 250 | 1 **currently unsupported**, use `"wsdl_headers": { "Authorization": "Basic …" },` instead, details: issue #92. 251 | 252 | ## Creating a model from a SOAP data source 253 | 254 | Instead of defining a data source with `datasources.json`, you can define a data source in code; for example: 255 | 256 | ```js 257 | ds.once('connected', function () { 258 | 259 | // Create the model 260 | var PeriodictableService = ds.createModel('PeriodictableService', {}); 261 | 262 | // External PeriodTable WebService operation exposed as REST APIs through LoopBack 263 | PeriodictableService.atomicnumber = function (elementName, cb) { 264 | PeriodictableService.GetAtomicNumber({ElementName: elementName || 'Copper'}, function (err, response) { 265 | var result = response; 266 | cb(err, result); 267 | }); 268 | }; 269 | ... 270 | } 271 | ``` 272 | 273 | ## Extending a model to wrap and mediate SOAP operations 274 | 275 | You can extend a LoopBack model to wrap or mediate SOAP operations 276 | and define new methods. 277 | The following example simplifies the `GetAtomicNumber` operation: 278 | 279 | ```javascript 280 | periodictableperiodictableSoap.GetAtomicNumber = function(GetAtomicNumber, callback) { 281 | periodictableperiodictableSoap.GetAtomicNumber(GetAtomicNumber, function (err, response) { 282 | var result = response; 283 | callback(err, result); 284 | }); 285 | } 286 | ``` 287 | 288 | ## Creating a model from a SOAP data source 289 | 290 | The SOAP connector loads WSDL documents asynchronously. 291 | As a result, the data source won't be ready to create models until it's connected. 292 | The recommended way is to use an event handler for the 'connected' event; for example 293 | as shown below. 294 | 295 | Once you define the model, you can extend it to wrap or mediate SOAP operations 296 | and define new methods. The example below shows adding a LoopBack remote method 297 | for the SOAP service's `GetAtomicNumber` operation. 298 | 299 | ```javascript 300 | ... 301 | ds.once('connected', function () { 302 | 303 | // Create the model 304 | var PeriodictableService = ds.createModel('PeriodictableService', {}); 305 | 306 | // External PeriodTable WebService operation exposed as REST APIs through LoopBack 307 | PeriodictableService.atomicnumber = function (elementName, cb) { 308 | PeriodictableService.GetAtomicNumber({ElementName: elementName || 'Copper'}, function (err, response) { 309 | var result = response; 310 | cb(err, result); 311 | }); 312 | }; 313 | 314 | // Map to REST/HTTP 315 | loopback.remoteMethod( 316 | PeriodictableService.atomicnumber, { 317 | accepts: [ 318 | {arg: 'elementName', type: 'string', required: true, 319 | http: {source: 'query'}} 320 | ], 321 | returns: {arg: 'result', type: 'object', root: true}, 322 | http: {verb: 'get', path: '/GetAtomicNumber'} 323 | } 324 | ); 325 | }) 326 | ... 327 | ``` 328 | 329 | ## Example 330 | 331 | For a complete example using the LoopBack SOAP connector in LoopBack 4, see [SOAP calculator tutorial](https://loopback.io/doc/en/lb4/soap-calculator-tutorial.html). 332 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security advisories 4 | 5 | Security advisories can be found on the 6 | [LoopBack website](https://loopback.io/doc/en/sec/index.html). 7 | 8 | ## Reporting a vulnerability 9 | 10 | If you think you have discovered a new security issue with any LoopBack package, 11 | **please do not report it on GitHub**. Instead, send an email to 12 | [security@loopback.io](mailto:security@loopback.io) with the following details: 13 | 14 | - Full description of the vulnerability. 15 | - Steps to reproduce the issue. 16 | - Possible solutions. 17 | 18 | If you are sending us any logs as part of the report, then make sure to redact 19 | any sensitive data from them. -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2017,2021. All Rights Reserved. 2 | // Node module: loopback-connector-soap 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | const isCI = process.env.CI; 7 | module.exports = { 8 | extends: [ 9 | '@commitlint/config-conventional', 10 | ], 11 | rules: { 12 | 'header-max-length': [2, 'always', 100], 13 | 'body-leading-blank': [2, 'always'], 14 | 'footer-leading-blank': [0, 'always'], 15 | // Only enforce the rule if CI flag is not set. This is useful for release 16 | // commits to skip DCO 17 | 'signed-off-by': [isCI ? 0 : 2, 'always', 'Signed-off-by:'], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": [ 3 | { 4 | "title": "LoopBack SOAP Connector", 5 | "depth": 2 6 | }, 7 | "lib/soap-connector.js", 8 | ], 9 | "codeSectionDepth": 3 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013,2018. All Rights Reserved. 2 | // Node module: loopback-connector-soap 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var SG = require('strong-globalize'); 7 | SG.SetRootDir(__dirname); 8 | 9 | module.exports = require('./lib/soap-connector'); 10 | -------------------------------------------------------------------------------- /intl/cs/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Metoda nebyla nalezena v typech portu {{WSDL}}: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Metoda nebyla nalezena v typech portu WSDL: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Časový limit připojení po {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NEPŘIPOJENO" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Methode nicht gefunden in {{WSDL}}-Porttypen: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Methode nicht gefunden in WSDL-Porttypen: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Zeitlimitüberschreitung bei Verbindungsherstellung nach {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NICHT verbunden" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Method not found in {{WSDL}} port types: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Method not found in WSDL port types: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Timeout in connecting after {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NOT Connected" 6 | } 7 | -------------------------------------------------------------------------------- /intl/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Método no encontrado en los tipos de puerto {{WSDL}}: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Método no encontrado en los tipos de puerto WSDL: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Tiempo de espera agotado al conectarse después de {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NO conectado" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Méthode introuvable dans les types de port {{WSDL}} : {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Méthode introuvable dans les types de port WSDL : {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Expiration du délai de connexion après {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NON connecté" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Metodo non trovato nei tipi di porta {{WSDL}}: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Metodo non trovato nei tipi di porta WSDL: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Timeout nella connessione dopo {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NON connesso" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "{{WSDL}} ポート・タイプでメソッドが見つかりません: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "WSDL ポート・タイプでメソッドが見つかりません: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "接続は {0} ミリ秒後にタイムアウトになります", 5 | "820b82f7e918976379aa812e78660c3a": "未接続" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "{{WSDL}} 포트 유형에서 메소드를 찾을 수 없음: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "WSDL 포트 유형에서 메소드를 찾을 수 없음: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "{0}밀리초 후 연결 제한시간 초과", 5 | "820b82f7e918976379aa812e78660c3a": "연결되지 않음" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Methode niet gevonden in {{WSDL}}-poorttypen: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Methode niet gevonden in WSDL-poorttypen: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Timeout voor het maken van verbinding na {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "GEEN verbinding tot stand gebracht" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Nie znaleziono metody w typach portów {{WSDL}}: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Nie znaleziono metody w typach portów WSDL: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Przekroczono limit czasu połączenia po {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NIE połączono" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/pt/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Método não localizado nos tipos de porta {{WSDL}}: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Método não localizado nos tipos de porta WSDL: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Tempo limite na conexão após {0} ms", 5 | "820b82f7e918976379aa812e78660c3a": "NÃO Conectado" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "Метод не найден в типах портов {{WSDL}}: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "Метод не найден в типах портов WSDL: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "Тайм-аут соединения наступает через {0} мс", 5 | "820b82f7e918976379aa812e78660c3a": "НЕ подключено" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/tr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "{{WSDL}} kapı tiplerinde yöntem bulunamadı: {0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "WSDL kapı tiplrinde yöntem bulunamadı: {0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "{0} milisaniyeden sonra bağlantıda zaman aşımı oluştu", 5 | "820b82f7e918976379aa812e78660c3a": "Bağlı DEĞİL" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/zh-Hans/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "在 {{WSDL}} 端口类型中找不到方法:{0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "在 WSDL 端口类型中找不到方法:{0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "在 {0} 毫秒后连接超时", 5 | "820b82f7e918976379aa812e78660c3a": "未连接" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /intl/zh-Hant/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "349b9be10e4084f59d8efc0c207a77a9": "在 {{WSDL}} 埠類型中找不到方法:{0}", 3 | "6801f7ace0d0ea9ec69f8d0f11d3b676": "在 WSDL 埠類型中找不到方法:{0}", 4 | "791ab3031a73ede03f7d6299a85e8289": "連接 {0} 毫秒之後逾時", 5 | "820b82f7e918976379aa812e78660c3a": "「未」連接" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /lib/http.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2015,2018. All Rights Reserved. 2 | // Node module: loopback-connector-soap 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | const req = require('postman-request'); 9 | const util = require('util'); 10 | const url = require('url'); 11 | const {soap: {HttpClient}} = require('strong-soap'); 12 | const debug = require('debug')('loopback:connector:soap:http'); 13 | const {version: VERSION} = require('../package.json'); 14 | 15 | function LBHttpClient(options, connector) { 16 | if (!(this instanceof LBHttpClient)) { 17 | return new LBHttpClient(options, connector); 18 | } 19 | const httpClient = new HttpClient(this, options); 20 | this.req = req; 21 | if (options && options.requestOptions) { 22 | this.req = req.defaults(options.requestOptions); 23 | } 24 | this.connector = connector; 25 | } 26 | 27 | util.inherits(LBHttpClient, HttpClient); 28 | 29 | /** 30 | * Build the HTTP request (method, uri, headers, ...) 31 | * @param {String} rurl The resource url 32 | * @param {Object|String} data The payload 33 | * @param {Object} exheaders Extra http headers 34 | * @param {Object} exoptions Extra options 35 | * @returns {Object} The http request object for the `request` module 36 | */ 37 | LBHttpClient.prototype.buildRequest = function(rurl, data, exheaders, exoptions) { 38 | const curl = url.parse(rurl); 39 | // const secure = curl.protocol === 'https:'; 40 | const host = curl.hostname; 41 | const port = parseInt(curl.port, 10); 42 | // const path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join(''); 43 | const method = data ? 'POST' : 'GET'; 44 | const headers = { 45 | 'User-Agent': `loopback-connector-soap/${VERSION}`, 46 | 'Accept': 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8', 47 | 'Accept-Encoding': 'none', 48 | 'Accept-Charset': 'utf-8', 49 | 'Connection': 'close', 50 | 'Host': host + (isNaN(port) ? '' : `:${port}`) 51 | }; 52 | let attr; 53 | 54 | if (typeof data === 'string') { 55 | headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); 56 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 57 | } 58 | 59 | exheaders = exheaders || {}; 60 | for (attr in exheaders) { 61 | headers[attr] = exheaders[attr]; 62 | } 63 | 64 | const options = { 65 | uri: curl, 66 | method: method, 67 | headers: headers, 68 | followAllRedirects: true 69 | }; 70 | 71 | if (data != null) { 72 | options.body = data; 73 | } 74 | 75 | exoptions = exoptions || {}; 76 | for (attr in exoptions) { 77 | options[attr] = exoptions[attr]; 78 | } 79 | debug('Http request: %j', options); 80 | return options; 81 | }; 82 | 83 | LBHttpClient.prototype.request = function(rurl, data, callback, exheaders, exoptions) { 84 | const self = this; 85 | const options = self.buildRequest(rurl, data, exheaders, exoptions); 86 | const context = { 87 | req: options 88 | }; 89 | 90 | function invokeWebService(context, done) { 91 | self.req(options, function(error, res, body) { 92 | if (error) { 93 | return done(error); 94 | } 95 | 96 | context.res = res; 97 | body = self.handleResponse(options, res, body); 98 | return done(null, res, body); 99 | }); 100 | } 101 | 102 | if (self.connector && typeof self.connector.notifyObserversAround === 'function') { 103 | // Now node-soap calls request to load WSDLs 104 | self.connector.notifyObserversAround('execute', context, invokeWebService, callback); 105 | } else { 106 | invokeWebService(context, callback); 107 | } 108 | return options; 109 | }; 110 | 111 | module.exports = LBHttpClient; 112 | -------------------------------------------------------------------------------- /lib/soap-connector.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013,2018. All Rights Reserved. 2 | // Node module: loopback-connector-soap 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | const g = require('strong-globalize')(); 9 | const {soap} = require('strong-soap'); 10 | const debug = require('debug')('loopback:connector:soap'); 11 | const HttpClient = require('./http'); 12 | 13 | /** 14 | * Export the initialize method to loopback-datasource-juggler 15 | * @param {DataSource} dataSource The data source object 16 | * @param callback 17 | */ 18 | exports.initialize = function initializeDataSource(dataSource, callback) { 19 | dataSource.connector = new SOAPConnector(dataSource.settings || {}); 20 | dataSource.connector.dataSource = dataSource; 21 | 22 | return dataSource.connector.connect(callback); 23 | }; 24 | 25 | /** 26 | * The SOAPConnector constructor 27 | * @param {Object} settings The connector settings 28 | * @constructor 29 | */ 30 | function SOAPConnector(settings) { 31 | this.settings = settings || {}; 32 | this.endpoint = this.settings.endpoint || this.settings.url; // The endpoint url 33 | this.wsdl = this.settings.wsdl || `${this.endpoint}?wsdl`; // URL or path to the url 34 | this.settings.httpClient = new HttpClient(this.settings, this); 35 | this._models = {}; 36 | 37 | if (this.settings.ignoredNamespaces == null) { 38 | // Some WSDLs use tns as the prefix. Without the customization below, it 39 | // will remove tns: for qualified elements 40 | this.settings.ignoredNamespaces = { 41 | namespaces: [], 42 | override: true 43 | } 44 | } 45 | 46 | if (debug.enabled) { 47 | debug('Settings: %j', this.settings); 48 | } 49 | 50 | this.DataAccessObject = function () { 51 | // Dummy function 52 | }; 53 | } 54 | 55 | SOAPConnector.prototype.connect = function (cb) { 56 | const self = this; 57 | if (self.client) { 58 | process.nextTick(function () { 59 | cb && cb(null, self.client); 60 | }); 61 | return; 62 | } 63 | if (debug.enabled) { 64 | debug('Reading wsdl: %s', self.wsdl); 65 | } 66 | soap.createClient(self.wsdl, self.settings, function (err, client) { 67 | if (!err) { 68 | if (debug.enabled) { 69 | debug('wsdl loaded: %s', self.wsdl); 70 | } 71 | if (self.settings.security || self.settings.username) { 72 | let sec = null; 73 | const secConfig = self.settings.security || self.settings; 74 | if (debug.enabled) { 75 | debug('configuring security: %j', secConfig); 76 | } 77 | switch (secConfig.scheme) { 78 | case 'WS': 79 | case 'WSSecurity': 80 | sec = new soap.WSSecurity( 81 | secConfig.username, 82 | secConfig.password, 83 | secConfig.passwordType || secConfig.options 84 | ); 85 | break; 86 | case 'WSSecurityCert': 87 | sec = new soap.WSSecurityCert( 88 | secConfig.privatePEM, 89 | secConfig.publicP12PEM, 90 | secConfig.password, 91 | secConfig.encoding 92 | ); 93 | break; 94 | case 'ClientSSL': 95 | if (secConfig.pfx) { 96 | sec = new soap.ClientSSLSecurityPFX( 97 | secConfig.pfx, 98 | secConfig.passphrase, 99 | secConfig.options 100 | ); 101 | } else { 102 | sec = new soap.ClientSSLSecurity( 103 | secConfig.keyPath || secConfig.key, 104 | secConfig.certPath || secConfig.cert, 105 | secConfig.ca || secConfig.caPath, 106 | secConfig.options); 107 | } 108 | break; 109 | case 'Bearer': 110 | sec = new soap.BearerSecurity(secConfig.token, secConfig.options); 111 | break; 112 | case 'BasicAuth': 113 | default: 114 | sec = new soap.BasicAuthSecurity(secConfig.username, secConfig.password, secConfig.options); 115 | break; 116 | } 117 | 118 | if (sec) { 119 | client.setSecurity(sec); 120 | } 121 | } 122 | 123 | if (self.settings.soapAction || self.settings.SOAPAction) { 124 | client.setSOAPAction(self.settings.soapAction || self.settings.SOAPAction); 125 | } 126 | 127 | if (Array.isArray(self.settings.soapHeaders)) { 128 | self.settings.soapHeaders.forEach(header => { 129 | if (debug.enabled) { 130 | debug('adding soap header: %j', header); 131 | } 132 | if (typeof header === 'object') { 133 | for (const item in header.element) { 134 | const elementValue = header.element[item]; 135 | const xml = `<${item} xmlns="${header.namespace}">${elementValue}`; 136 | client.addSoapHeader(xml); 137 | } 138 | } else if (typeof header === 'string') { 139 | client.addSoapHeader(header); 140 | } 141 | }); 142 | } else if (self.settings.soapHeaders) { 143 | client.addSoapHeader(self.settings.soapHeaders) 144 | } 145 | 146 | if (typeof self.settings.httpHeaders == 'object') { 147 | Object.entries(self.settings.httpHeaders).forEach(([key, value]) => { 148 | if (debug.enabled) { 149 | debug('adding http header: %j', { [key]: value }); 150 | } 151 | client.addHttpHeader(key, value) 152 | }) 153 | } 154 | 155 | client.connector = self; 156 | self.client = client; 157 | self.setupDataAccessObject(); 158 | } 159 | // If the wsdl is cached, the callback from soap.createClient is sync (BUG!) 160 | if (cb) { 161 | process.nextTick(function () { 162 | cb(err, client); 163 | }); 164 | } 165 | }, self.endpoint); 166 | }; 167 | 168 | /** 169 | * Find or derive the method name from service/port/operation 170 | * @param {String} serviceName The WSDL service name 171 | * @param {String} portName The WSDL port name 172 | * @param {String} operationName The WSDL operation name 173 | * @param {Object} dao The data access objecr 174 | * @returns {String} The method name 175 | * @private 176 | */ 177 | SOAPConnector.prototype._methodName = function (serviceName, portName, operationName, dao) { 178 | const methods = this.settings.operations || {}; 179 | for (const m in methods) { 180 | const method = methods[m]; 181 | if (method.service === serviceName && method.port === portName && 182 | (method.operation === operationName || 183 | method.operation === undefined && m === operationName)) { 184 | // Return the matched method name 185 | return m; 186 | } 187 | } 188 | 189 | if (dao && (operationName in dao)) { 190 | // The operation name exists, return full name 191 | return `${serviceName}_${portName}_${operationName}`; 192 | } else { 193 | return operationName; 194 | } 195 | }; 196 | 197 | function setRemoting(wsMethod) { 198 | wsMethod.shared = true; 199 | wsMethod.accepts = [ 200 | { 201 | arg: 'input', type: 'object', required: true, 202 | http: {source: 'body'} 203 | } 204 | ]; 205 | wsMethod.returns = {arg: 'output', type: 'object', root: true}; 206 | } 207 | 208 | function findKey(obj, val) { 209 | for (const n in obj) { 210 | if (obj[n] === val) { 211 | return n; 212 | } 213 | } 214 | return null; 215 | } 216 | 217 | function findMethod(client, name) { 218 | const services = client.wsdl.services; 219 | for(const s in services){ 220 | const service = services[s]; 221 | for (const p in service.ports) { 222 | const pt = service.ports[p]; 223 | for (const op in pt.binding.operations) { 224 | if (op === name) { 225 | return pt.binding.operations[op]; 226 | } 227 | } 228 | } 229 | } 230 | return null; 231 | } 232 | 233 | SOAPConnector.prototype.jsonToXML = function (method, json) { 234 | if (!json) { 235 | return ''; 236 | } 237 | 238 | const client = this.client; 239 | client.describe(); 240 | 241 | if (typeof method === 'string') { 242 | const m = findMethod(this.client, method); 243 | if(!m) { 244 | throw new Error(g.f('Method not found in WSDL port types: %s', m)); 245 | } else { 246 | method = m; 247 | } 248 | } 249 | 250 | const {input, descriptor: {input: {body: inputDecriptor}}} = method; 251 | 252 | let message = ''; 253 | if (input.message.parts) { 254 | message = client.xmlHandler.jsonToXml(null, null, inputDecriptor, json); 255 | } else if (typeof json === 'string') { 256 | message = json; 257 | } else { 258 | message = client.wsdl.objectToDocumentXML(input.$name, json, input.targetNSAlias, input.targetNamespace, input.$type); 259 | } 260 | return message.toString(); 261 | }; 262 | 263 | SOAPConnector.prototype.xmlToJSON = function (method, xml) { 264 | if(!xml) { 265 | return {}; 266 | } 267 | 268 | if (typeof method === 'string') { 269 | const m = findMethod(this.client, method); 270 | if(!m) { 271 | throw new Error(g.f('Method not found in {{WSDL}} port types: %s', m)); 272 | } else { 273 | method = m; 274 | } 275 | } 276 | 277 | const {input, output} = method; 278 | const json = this.client.xmlHandler.xmlToJson(null, xml); 279 | let root; 280 | let result; 281 | if(output.message.parts && output.message.parts.parameters && output.message.parts.parameters.element) 282 | { 283 | root = output.message.parts.parameters.element.$name; 284 | result = json.Body[root]; 285 | } else if(input.message.parts && input.message.parts.parameters && input.message.parts.parameters.element) 286 | { 287 | root = input.message.parts.parameters.element.$name; 288 | result = json.Body[root]; 289 | } 290 | // RPC/literal response body may contain elements with added suffixes I.E. 291 | // 'Response', or 'Output', or 'Out' 292 | // This doesn't necessarily equal the output message name. See WSDL 1.1 Section 2.4.5 293 | if(!result){ 294 | result = json.Body[output.parent.$name.replace(/(?:Out(?:put)?|Response)$/, '')]; 295 | } 296 | return result; 297 | }; 298 | 299 | /** 300 | * 301 | * @private 302 | * @returns {*} 303 | */ 304 | SOAPConnector.prototype.setupDataAccessObject = function () { 305 | const self = this; 306 | if (this.wsdlParsed && this.DataAccessObject) { 307 | return this.DataAccessObject; 308 | } 309 | 310 | this.wsdlParsed = true; 311 | 312 | this.DataAccessObject.xmlToJSON = SOAPConnector.prototype.xmlToJSON.bind(self); 313 | this.DataAccessObject.jsonToXML = SOAPConnector.prototype.jsonToXML.bind(self); 314 | 315 | for (const s in this.client.wsdl.services) { 316 | const service = this.client[s]; 317 | for (const p in service) { 318 | const port = service[p]; 319 | for (const m in port) { 320 | const method = port[m]; 321 | if (debug.enabled) { 322 | debug('Adding method: %s %s %s', s, p, m); 323 | } 324 | 325 | const methodName = this._methodName(s, p, m, this.DataAccessObject); 326 | if (debug.enabled) { 327 | debug('Method name: %s', methodName); 328 | } 329 | 330 | const wsMethod = method.bind(this.client); 331 | wsMethod.jsonToXML = SOAPConnector.prototype.jsonToXML.bind(self, findMethod(self.client, m)); 332 | wsMethod.xmlToJSON = SOAPConnector.prototype.xmlToJSON.bind(self, findMethod(self.client, m)); 333 | this.DataAccessObject[methodName] = wsMethod; 334 | if (this.settings.remotingEnabled) { 335 | setRemoting(wsMethod); 336 | } 337 | } 338 | } 339 | } 340 | this.dataSource.DataAccessObject = this.DataAccessObject; 341 | for (const model in this._models) { 342 | if (debug.enabled) { 343 | debug('Mixing methods into : %s', model); 344 | } 345 | this.dataSource.mixin(this._models[model].model); 346 | } 347 | return this.DataAccessObject; 348 | }; 349 | 350 | /** 351 | * Hook for defining a model by the data source 352 | * @param {object} modelDef The model description 353 | */ 354 | SOAPConnector.prototype.define = function (modelDef) { 355 | const m = modelDef.model.modelName; 356 | this._models[m] = modelDef; 357 | }; 358 | 359 | /** 360 | * Get types associated with the connector 361 | * @returns {String[]} The types for the connector 362 | */ 363 | SOAPConnector.prototype.getTypes = function() { 364 | return ['soap']; 365 | }; 366 | 367 | /** 368 | * Attempt to test the connectivity that the soap driver depends on. 369 | */ 370 | 371 | SOAPConnector.prototype.ping = function (cb) { 372 | ready(this.dataSource, function(err) { 373 | if (err) { 374 | const connectionError = new Error(g.f('NOT Connected')); 375 | connectionError.originalError = err; 376 | return cb(connectionError); 377 | } 378 | 379 | return cb(); 380 | }); 381 | }; 382 | 383 | function ready(dataSource, cb) { 384 | if (dataSource.connected) { 385 | // Connected 386 | return process.nextTick(function() { 387 | cb(); 388 | }); 389 | } 390 | 391 | let timeoutHandle; 392 | 393 | function onConnected() { 394 | if (timeoutHandle) { 395 | clearTimeout(timeoutHandle); 396 | } 397 | 398 | cb(); 399 | } 400 | 401 | function onError(err) { 402 | // Remove the connected listener 403 | dataSource.removeListener('connected', onConnected); 404 | if (timeoutHandle) { 405 | clearTimeout(timeoutHandle); 406 | } 407 | const params = [].slice.call(args); 408 | const cb = params.pop(); 409 | if (typeof cb === 'function') { 410 | process.nextTick(function() { 411 | cb(err); 412 | }); 413 | } 414 | } 415 | 416 | dataSource.once('connected', onConnected); 417 | dataSource.once('error', onError); 418 | 419 | // Set up a timeout to cancel the invocation 420 | const timeout = dataSource.settings.connectionTimeout || 60000; 421 | timeoutHandle = setTimeout(function() { 422 | dataSource.removeListener('error', onError); 423 | self.removeListener('connected', onConnected); 424 | const params = [].slice.call(args); 425 | const cb = params.pop(); 426 | if (typeof cb === 'function') { 427 | cb(new Error(g.f('Timeout in connecting after %s ms', timeout))); 428 | } 429 | }, timeout); 430 | 431 | if (!dataSource.connecting) { 432 | dataSource.connect(); 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-connector-soap", 3 | "version": "8.0.19", 4 | "description": "LoopBack SOAP Web Services Connector", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "mocha -R spec --timeout 60000 test/*test.js" 11 | }, 12 | "dependencies": { 13 | "debug": "^4.0.1", 14 | "postman-request": "^2.88.1-postman.42", 15 | "strong-globalize": "^6.0.5", 16 | "strong-soap": "^4.0.0" 17 | }, 18 | "devDependencies": { 19 | "@commitlint/cli": "^19.0.0", 20 | "@commitlint/config-conventional": "^19.0.0", 21 | "loopback-datasource-juggler": "^5.0.0", 22 | "mocha": "^11.0.0", 23 | "nyc": "^17.0.0", 24 | "should": "^13.2.3" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/loopbackio/loopback-connector-soap.git" 29 | }, 30 | "copyright.owner": "IBM Corp.", 31 | "license": "MIT" 32 | } 33 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/test-certificate.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopbackio/loopback-connector-soap/39a8a558bd81872f04fe1c15db8fa6cc755db4ab/test/fixtures/test-certificate.p12 -------------------------------------------------------------------------------- /test/fixtures/test-certificate2.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopbackio/loopback-connector-soap/39a8a558bd81872f04fe1c15db8fa6cc755db4ab/test/fixtures/test-certificate2.pfx -------------------------------------------------------------------------------- /test/sample-req.json: -------------------------------------------------------------------------------- 1 | { 2 | "ZIP": "95131" 3 | } 4 | -------------------------------------------------------------------------------- /test/sample-req.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 95131 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/sample-res.json: -------------------------------------------------------------------------------- 1 | { 2 | "ForecastReturn": { 3 | "Success": "true", 4 | "ResponseText": "City Found", 5 | "State": "CA", 6 | "City": "Fremont", 7 | "WeatherStationCity": "Fremont", 8 | "ForecastResult": { 9 | "Forecast": [ 10 | { 11 | "Date": "2013-01-03T00:00:00", 12 | "WeatherID": "2", 13 | "Desciption": "Partly Cloudy", 14 | "Temperatures": { 15 | "MorningLow": "37", 16 | "DaytimeHigh": "56" 17 | }, 18 | "ProbabilityOfPrecipiation": { 19 | "Nighttime": "00", 20 | "Daytime": "00" 21 | } 22 | }, 23 | { 24 | "Date": "2013-01-04T00:00:00", 25 | "WeatherID": "2", 26 | "Desciption": "Partly Cloudy", 27 | "Temperatures": { 28 | "MorningLow": "38", 29 | "DaytimeHigh": "57" 30 | }, 31 | "ProbabilityOfPrecipiation": { 32 | "Nighttime": "00", 33 | "Daytime": "00" 34 | } 35 | }, 36 | { 37 | "Date": "2013-01-05T00:00:00", 38 | "WeatherID": "2", 39 | "Desciption": "Partly Cloudy", 40 | "Temperatures": { 41 | "MorningLow": "38", 42 | "DaytimeHigh": "58" 43 | }, 44 | "ProbabilityOfPrecipiation": { 45 | "Nighttime": "00", 46 | "Daytime": "10" 47 | } 48 | }, 49 | { 50 | "Date": "2013-01-06T00:00:00", 51 | "WeatherID": "2", 52 | "Desciption": "Partly Cloudy", 53 | "Temperatures": { 54 | "MorningLow": "42", 55 | "DaytimeHigh": "56" 56 | }, 57 | "ProbabilityOfPrecipiation": { 58 | "Nighttime": "20", 59 | "Daytime": "20" 60 | } 61 | }, 62 | { 63 | "Date": "2013-01-07T00:00:00", 64 | "WeatherID": "2", 65 | "Desciption": "Partly Cloudy", 66 | "Temperatures": { 67 | "MorningLow": "38", 68 | "DaytimeHigh": "59" 69 | }, 70 | "ProbabilityOfPrecipiation": { 71 | "Nighttime": "10", 72 | "Daytime": "10" 73 | } 74 | }, 75 | { 76 | "Date": "2013-01-08T00:00:00", 77 | "WeatherID": "2", 78 | "Desciption": "Partly Cloudy", 79 | "Temperatures": { 80 | "MorningLow": "39", 81 | "DaytimeHigh": "59" 82 | }, 83 | "ProbabilityOfPrecipiation": { 84 | "Nighttime": "10", 85 | "Daytime": "10" 86 | } 87 | }, 88 | { 89 | "Date": "2013-01-09T00:00:00", 90 | "WeatherID": "2", 91 | "Desciption": "Partly Cloudy", 92 | "Temperatures": { 93 | "MorningLow": "40", 94 | "DaytimeHigh": "58" 95 | }, 96 | "ProbabilityOfPrecipiation": { 97 | "Nighttime": "10", 98 | "Daytime": "10" 99 | } 100 | } 101 | ] 102 | } 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /test/sample-res.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | true 12 | City Found 13 | CA 14 | Fremont 15 | Fremont 16 | 17 | 18 | 2013-01-03T00:00:00 19 | 2 20 | Partly Cloudy 21 | 22 | 37 23 | 56 24 | 25 | 26 | 00 27 | 00 28 | 29 | 30 | 31 | 2013-01-04T00:00:00 32 | 2 33 | Partly Cloudy 34 | 35 | 38 36 | 57 37 | 38 | 39 | 00 40 | 00 41 | 42 | 43 | 44 | 2013-01-05T00:00:00 45 | 2 46 | Partly Cloudy 47 | 48 | 38 49 | 58 50 | 51 | 52 | 00 53 | 10 54 | 55 | 56 | 57 | 2013-01-06T00:00:00 58 | 2 59 | Partly Cloudy 60 | 61 | 42 62 | 56 63 | 64 | 65 | 20 66 | 20 67 | 68 | 69 | 70 | 2013-01-07T00:00:00 71 | 2 72 | Partly Cloudy 73 | 74 | 38 75 | 59 76 | 77 | 78 | 10 79 | 10 80 | 81 | 82 | 83 | 2013-01-08T00:00:00 84 | 2 85 | Partly Cloudy 86 | 87 | 39 88 | 59 89 | 90 | 91 | 10 92 | 10 93 | 94 | 95 | 96 | 2013-01-09T00:00:00 97 | 2 98 | Partly Cloudy 99 | 100 | 40 101 | 58 102 | 103 | 104 | 10 105 | 10 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /test/security.test.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014,2018. All Rights Reserved. 2 | // Node module: loopback-connector-soap 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | var fs = require('fs'), 9 | soap = require('strong-soap').soap, 10 | assert = require('assert'), 11 | DataSource = require('loopback-datasource-juggler').DataSource, 12 | path = require('path'); 13 | 14 | var test = {}; 15 | test.server = null; 16 | test.service = { 17 | StockQuoteService: { 18 | StockQuotePort: { 19 | GetLastTradePrice: function (args) { 20 | if (args.tickerSymbol === 'trigger error') { 21 | throw new Error('triggered server error'); 22 | } else { 23 | return { TradePrice: { price: 19.56 } }; 24 | } 25 | } 26 | } 27 | } 28 | }; 29 | 30 | describe('soap connector', function () { 31 | describe('http-based servers', function () { 32 | var http = require('http'); 33 | before(function (done) { 34 | fs.readFile(__dirname + '/wsdls/stockquote.wsdl', 'utf8', function (err, data) { 35 | assert.ok(!err); 36 | test.wsdl = data; 37 | 38 | test.server = http.createServer(function (req, res) { 39 | res.statusCode = 404; 40 | res.end(); 41 | }); 42 | 43 | test.server.listen(3000, null, null, function () { 44 | test.soapServer = soap.listen(test.server, '/stockquote', test.service, test.wsdl); 45 | test.soapServer.wsdl.options.attributesKey = 'attributes'; 46 | test.baseUrl = 47 | 'http://' + test.server.address().address + ":" + test.server.address().port; 48 | 49 | test.soapServer.authenticate = function (security) { 50 | var created, nonce, password, user, token; 51 | token = security.UsernameToken, user = token.Username, 52 | password = token.Password.$value, nonce = token.Nonce.$value, created = token.Created; 53 | return user === 'test' && password === soap.passwordDigest(nonce, created, 'testpass'); 54 | }; 55 | 56 | test.soapServer.log = function (type, data) { 57 | // type is 'received' or 'replied' 58 | }; 59 | 60 | done(); 61 | }); 62 | }); 63 | }); 64 | 65 | after(function (done) { 66 | test.server.close(function () { 67 | test.server = null; 68 | delete test.soapServer; 69 | test.soapServer = null; 70 | done(); 71 | }); 72 | }); 73 | 74 | it('should supports WSSecurity', function (done) { 75 | var ds = new DataSource('soap', 76 | { 77 | connector: require('../index'), 78 | security: { 79 | scheme: 'WS', 80 | username: 'test', 81 | password: 'testpass', 82 | passwordType: 'PasswordDigest' 83 | }, 84 | soapHeaders: [{ 85 | element: { myHeader: 'XYZ' }, 86 | prefix: 'p1', 87 | namespace: 'http://ns1' 88 | }], 89 | url: 'http://localhost:3000/stockquote' // The service endpoint 90 | }); 91 | ds.on('connected', function () { 92 | var StockQuote = ds.createModel('StockQuote', {}); 93 | StockQuote.GetLastTradePrice({ TradePriceRequest: { tickerSymbol: 'IBM' } }, function (err, quote) { 94 | assert.equal(quote.price, '19.56'); 95 | done(err); 96 | }); 97 | }); 98 | }); 99 | 100 | it('should reject bad username/password with WSSecurity', function (done) { 101 | var ds = new DataSource('soap', 102 | { 103 | connector: require('../index'), 104 | security: { 105 | scheme: 'WS', 106 | username: 'test', 107 | password: 'wrongpass', 108 | passwordType: 'PasswordDigest' 109 | }, 110 | url: 'http://localhost:3000/stockquote' // The service endpoint 111 | }); 112 | ds.on('connected', function () { 113 | var StockQuote = ds.createModel('StockQuote', {}); 114 | StockQuote.GetLastTradePrice({ TradePriceRequest: { tickerSymbol: 'IBM' } }, function (err, quote) { 115 | assert(err); 116 | done(); 117 | }); 118 | }); 119 | }); 120 | 121 | // FIXME: [rfeng] node-soap module doesn't support BasicAuth on the server side yet 122 | /* 123 | it('should supports BasicAuthSecurity', function (done) { 124 | var ds = loopback.createDataSource('soap', 125 | { 126 | connector: require('../index'), 127 | security: { 128 | scheme: 'BasicAuth', 129 | username: 'test', 130 | password: 'testpass' 131 | }, 132 | url: 'http://localhost:3000/stockquote' // The service endpoint 133 | }); 134 | ds.on('connected', function () { 135 | var StockQuote = ds.createModel('StockQuote', {}); 136 | StockQuote.GetLastTradePrice({tickerSymbol: 'IBM'}, function (err, quote) { 137 | assert.equal(quote.price, '19.56'); 138 | done(err); 139 | }); 140 | }); 141 | 142 | }); 143 | */ 144 | 145 | it('should supports soap headers', function (done) { 146 | var ds = new DataSource('soap', 147 | { 148 | connector: require('../index'), 149 | security: { 150 | scheme: 'WS', 151 | username: 'test', 152 | password: 'testpass', 153 | passwordType: 'PasswordDigest' 154 | }, 155 | soapHeaders: [{ 156 | element: { myHeader: 'XYZ' }, 157 | prefix: 'p1', 158 | namespace: 'http://ns1' 159 | }], 160 | url: 'http://localhost:3000/stockquote' // The service endpoint 161 | }); 162 | ds.on('connected', function () { 163 | var StockQuote = ds.createModel('StockQuote', {}); 164 | StockQuote.GetLastTradePrice({ TradePriceRequest: { tickerSymbol: 'IBM' } }, function (err, quote) { 165 | assert.equal(quote.price, '19.56'); 166 | done(err); 167 | }); 168 | }); 169 | }); 170 | }); 171 | 172 | describe('https-based servers', function () { 173 | var https = require('https'); 174 | var certPath = path.resolve(__dirname, 'fixtures', 'test-certificate2.pfx'); 175 | before(function (done) { 176 | fs.readFile(__dirname + '/wsdls/stockquote.wsdl', 'utf8', function (err, data) { 177 | assert.ok(!err); 178 | test.wsdl = data; 179 | 180 | // FIXME(kjdelisle): We probably shouldn't be using the same cert... 181 | test.server = https.createServer({ 182 | pfx: fs.readFileSync(certPath), 183 | passphrase: 'abc123', 184 | rejectUnauthorized: false, 185 | requestCert: true, 186 | }, function (req, res) { 187 | res.statusCode = 404; 188 | res.end(); 189 | }); 190 | 191 | test.server.listen(3000, null, null, function () { 192 | test.soapServer = soap.listen(test.server, '/stockquote', test.service, test.wsdl); 193 | test.soapServer.wsdl.options.attributesKey = 'attributes'; 194 | test.baseUrl = 'https://' + test.server.address().address + ":" + test.server.address().port; 195 | 196 | test.soapServer.log = function (type, data) { 197 | // type is 'received' or 'replied' 198 | }; 199 | 200 | done(); 201 | }); 202 | }); 203 | }); 204 | 205 | after(function (done) { 206 | test.server.close(function () { 207 | test.server = null; 208 | delete test.soapServer; 209 | test.soapServer = null; 210 | done(); 211 | }); 212 | }); 213 | it('should support ClientSSL with PFX', function (done) { 214 | var ds = new DataSource('soap', 215 | { 216 | connector: require('../index'), 217 | security: { 218 | scheme: 'ClientSSL', 219 | pfx: certPath, 220 | passphrase: 'abc123', 221 | username: 'test', 222 | password: 'testpass', 223 | passwordType: 'PasswordDigest', 224 | }, 225 | requestOptions: { 226 | rejectUnauthorized: false, 227 | }, 228 | soapHeaders: [{ 229 | element: { myHeader: 'XYZ' }, 230 | prefix: 'p1', 231 | namespace: 'http://ns1' 232 | }], 233 | url: 'https://localhost:3000/stockquote' // The service endpoint 234 | }); 235 | ds.on('connected', function () { 236 | var StockQuote = ds.createModel('StockQuote', {}); 237 | StockQuote.GetLastTradePrice({ TradePriceRequest: { tickerSymbol: 'IBM' } }, 238 | function (err, quote) { 239 | assert.equal(quote.price, '19.56'); 240 | done(err); 241 | }); 242 | }); 243 | }); 244 | }); 245 | }); 246 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014,2018. All Rights Reserved. 2 | // Node module: loopback-connector-soap 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | var should = require('should'); 9 | var DataSource = require('loopback-datasource-juggler').DataSource; 10 | var path = require('path'); 11 | var fs = require('fs'); 12 | var assert = require('assert'); 13 | 14 | describe('soap connector', function () { 15 | describe('wsdl configuration', function () { 16 | it('should be able to derive wsdl from url', function (done) { 17 | var ds = new DataSource('soap', 18 | { 19 | connector: require('../index'), 20 | url: 'http://www.dneonline.com/calculator.asmx' // The service endpoint 21 | }); 22 | ds.on('connected', function () { 23 | ds.connector.should.have.property('client'); 24 | ds.connector.client.should.have.property('wsdl'); 25 | return done(); 26 | }); 27 | }); 28 | 29 | it('should be able to support local wsdl', function (done) { 30 | var ds = new DataSource('soap', 31 | { 32 | connector: require('../index'), 33 | wsdl: path.join(__dirname, 'wsdls/weather.wsdl') 34 | }); 35 | ds.on('connected', function () { 36 | ds.connector.should.have.property('client'); 37 | ds.connector.client.should.have.property('wsdl'); 38 | return done(); 39 | }); 40 | }); 41 | 42 | it('should be able to support remote wsdl', function (done) { 43 | var ds = new DataSource('soap', 44 | { 45 | connector: require('../index'), 46 | wsdl: 'http://www.dneonline.com/calculator.asmx?wsdl' 47 | }); 48 | ds.on('connected', function () { 49 | ds.connector.should.have.property('client'); 50 | ds.connector.client.should.have.property('wsdl'); 51 | return done(); 52 | }); 53 | }); 54 | }); 55 | 56 | describe('client settings', function () { 57 | it('should have httpHeaders set', function (done) { 58 | var ds = new DataSource('soap', 59 | { 60 | connector: require('../index'), 61 | wsdl: 'http://www.dneonline.com/calculator.asmx?wsdl', // The service endpoint, 62 | httpHeaders: { "my-custom-header": "my-custom-header-value" } 63 | }); 64 | 65 | ds.on('connected', function () { 66 | ds.connector.client.getHttpHeaders().should.eql({ "my-custom-header": "my-custom-header-value" }) 67 | return done(); 68 | }); 69 | }); 70 | }); 71 | 72 | describe('models', function () { 73 | describe('models without remotingEnabled', function () { 74 | var ds; 75 | before(function (done) { 76 | ds = new DataSource('soap', 77 | { 78 | connector: require('../index'), 79 | wsdl: path.join(__dirname, 'wsdls/calculator_external.wsdl') 80 | }); 81 | ds.on('connected', function () { 82 | return done(); 83 | }); 84 | }); 85 | 86 | it('should create models', function (done) { 87 | var Calculator = ds.createModel('CalculatorService', {}); 88 | // Short method names 89 | (typeof Calculator.Add).should.eql('function'); 90 | // Full method names for SOAP 12 operations that conflict with the simple ones 91 | (typeof Calculator.Calculator_CalculatorSoap12_Add).should.eql('function'); 92 | 93 | return done(); 94 | }); 95 | 96 | it('should support model methods', function (done) { 97 | var CalculatorService = ds.createModel('CalculatorService', {}); 98 | 99 | CalculatorService.Add({ 100 | intA: 1, 101 | intB: 2 102 | }, function (err, response) { 103 | assert.ok(typeof response.AddResult === 'number'); 104 | return done(); 105 | }); 106 | }); 107 | 108 | it('should support model methods as promises', function (done) { 109 | var CalculatorService = ds.createModel('CalculatorService', {}); 110 | 111 | CalculatorService.Add({ 112 | intA: 1, 113 | intB: 2 114 | }).then(function (response) { 115 | assert.ok(typeof response.result.AddResult === 'number'); 116 | return done(); 117 | }, done); 118 | }); 119 | }); 120 | 121 | describe('XML/JSON conversion utilities', function () { 122 | var ds; 123 | var sampleReq, sampleReqJson, sampleRes, sampleResJson; 124 | before(function (done) { 125 | sampleReq = fs.readFileSync(path.join(__dirname, 'sample-req.xml'), 'utf-8'); 126 | sampleReqJson = fs.readFileSync(path.join(__dirname, 'sample-req.json'), 'utf-8'); 127 | sampleRes = fs.readFileSync(path.join(__dirname, 'sample-res.xml'), 'utf-8'); 128 | sampleResJson = fs.readFileSync(path.join(__dirname, 'sample-res.json'), 'utf-8'); 129 | ds = new DataSource('soap', 130 | { 131 | connector: require('../index'), 132 | wsdl: path.join(__dirname, 'wsdls/weather.wsdl') 133 | }); 134 | ds.on('connected', function () { 135 | return done(); 136 | }); 137 | }); 138 | 139 | it('should support xmlToJSON methods', function () { 140 | var WeatherService = ds.createModel('WeatherService', {}); 141 | assert.equal(typeof WeatherService.xmlToJSON, 'function'); 142 | assert.equal(typeof WeatherService.GetCityForecastByZIP.xmlToJSON, 'function'); 143 | 144 | var json = WeatherService.xmlToJSON('GetCityForecastByZIP', sampleReq); 145 | assert.deepEqual(json, JSON.parse(sampleReqJson)); 146 | 147 | json = WeatherService.GetCityForecastByZIP.xmlToJSON(sampleReq); 148 | assert.deepEqual(json, JSON.parse(sampleReqJson)); 149 | 150 | json = WeatherService.GetCityForecastByZIP.xmlToJSON(sampleRes); 151 | assert.deepEqual(json, JSON.parse(sampleResJson)); 152 | }); 153 | 154 | it('should support jsonToXML methods', function () { 155 | var WeatherService = ds.createModel('WeatherService', {}); 156 | assert.equal(typeof WeatherService.jsonToXML, 'function'); 157 | assert.equal(typeof WeatherService.GetCityForecastByZIP.jsonToXML, 'function'); 158 | var xml = WeatherService.jsonToXML('GetCityForecastByZIP', JSON.parse(sampleReqJson)); 159 | assert.equal(xml, '95131' + 162 | ''); 163 | 164 | xml = WeatherService.GetCityForecastByZIP.jsonToXML(JSON.parse(sampleReqJson)); 165 | assert.equal(xml, '95131' + 168 | ''); 169 | }); 170 | }); 171 | 172 | describe('models with remotingEnabled', function () { 173 | var ds; 174 | before(function (done) { 175 | ds = new DataSource('soap', 176 | { 177 | connector: require('../index'), 178 | remotingEnabled: true, 179 | wsdl: path.join(__dirname, 'wsdls/weather.wsdl') 180 | }); 181 | ds.on('connected', function () { 182 | return done(); 183 | }); 184 | }); 185 | 186 | it('should create models', function (done) { 187 | var WeatherService = ds.createModel('WeatherService', {}); 188 | 189 | // Short method names 190 | (typeof WeatherService.GetCityForecastByZIP).should.eql('function'); 191 | WeatherService.GetCityForecastByZIP.shared.should.be.true; 192 | // Full method names for SOAP 12 operations that conflict with the simple ones 193 | (typeof WeatherService.Weather_WeatherSoap12_GetWeatherInformation).should.eql('function'); 194 | WeatherService.Weather_WeatherSoap12_GetWeatherInformation.shared.should.be.true; 195 | 196 | return done(); 197 | }); 198 | }); 199 | 200 | describe('models with operations', function () { 201 | var ds; 202 | before(function (done) { 203 | ds = new DataSource('soap', 204 | { 205 | connector: require('../index'), 206 | wsdl: path.join(__dirname, 'wsdls/weather.wsdl'), 207 | operations: { 208 | weatherInfo: { 209 | service: 'Weather', 210 | port: 'WeatherSoap', 211 | operation: 'GetWeatherInformation' 212 | }, 213 | cityForecastByZIP: { 214 | service: 'Weather', 215 | port: 'WeatherSoap', 216 | operation: 'GetCityForecastByZIP' 217 | }, 218 | cityWeatherByZIP: { 219 | service: 'Weather', 220 | port: 'WeatherSoap', 221 | operation: 'GetCityWeatherByZIP' 222 | } 223 | } 224 | }); 225 | ds.on('connected', function () { 226 | return done(); 227 | }); 228 | }); 229 | 230 | it('should create mapped methods for operations', function (done) { 231 | var WeatherService = ds.createModel('WeatherService', {}); 232 | 233 | // Operation mapped method names are defined 234 | (typeof WeatherService.cityForecastByZIP).should.eql('function'); 235 | (typeof WeatherService.cityWeatherByZIP).should.eql('function'); 236 | (typeof WeatherService.weatherInfo).should.eql('function'); 237 | 238 | // Actual method names are defined 239 | (typeof WeatherService.GetCityForecastByZIP).should.eql('function'); 240 | (typeof WeatherService.GetCityWeatherByZIP).should.eql('function'); 241 | (typeof WeatherService.GetWeatherInformation).should.eql('function'); 242 | 243 | // Full method names for SOAP 12 operations are not defined (operations method defs prevent these from being created) 244 | (typeof WeatherService.Weather_WeatherSoap12_GetWeatherInformation).should.eql('undefined'); 245 | (typeof WeatherService.Weather_WeatherSoap12_GetCityForecastByZIP).should.eql('undefined'); 246 | (typeof WeatherService.Weather_WeatherSoap12_GetCityWeatherByZIP).should.eql('undefined'); 247 | 248 | return done(); 249 | }); 250 | }); 251 | 252 | describe('soap invocations', function () { 253 | var ds; 254 | var CalculatorService; 255 | 256 | before(function (done) { 257 | ds = new DataSource('soap', 258 | { 259 | connector: require('../index'), 260 | wsdl: 'http://www.dneonline.com/calculator.asmx?wsdl', // The url to WSDL 261 | url: 'http://www.dneonline.com/calculator.asmx', // The service endpoint 262 | // Map SOAP service/port/operation to Node.js methods 263 | operations: { 264 | // The key is the method name 265 | add: { 266 | service: 'Calculator', // The WSDL service name 267 | port: 'CalculatorSoap', // The WSDL port name 268 | operation: 'Add' // The WSDL operation name 269 | }, 270 | multiply: { 271 | service: 'Calculator', // The WSDL service name 272 | port: 'CalculatorSoap', // The WSDL port name 273 | operation: 'Multiply' // The WSDL operation name 274 | } 275 | } 276 | }); 277 | ds.on('connected', function () { 278 | CalculatorService = ds.createModel('CalculatorService', {}); 279 | return done(); 280 | }); 281 | }); 282 | 283 | it('should invoke the add', function (done) { 284 | CalculatorService.add({ 285 | intA: 1, 286 | intB: 2 287 | }, function (err, response) { 288 | assert.ok(typeof response.AddResult === 'number'); 289 | return done(); 290 | }); 291 | }); 292 | 293 | it('should invoke the multiply', function (done) { 294 | CalculatorService.multiply({ 295 | intA: 2, 296 | intB: 2 297 | }, function (err, response) { 298 | assert.ok(typeof response.MultiplyResult === 'number'); 299 | return done(); 300 | }); 301 | }); 302 | 303 | it('should invoke hooks', function (done) { 304 | var events = []; 305 | var connector = ds.connector; 306 | connector.observe('before execute', function (ctx, next) { 307 | assert(ctx.req); 308 | events.push('before execute'); 309 | return next(); 310 | }); 311 | connector.observe('after execute', function (ctx, next) { 312 | assert(ctx.res); 313 | events.push('after execute'); 314 | return next(); 315 | }); 316 | CalculatorService.add({ 317 | intA: 1, 318 | intB: 2 319 | }, function () { 320 | assert.deepEqual(events, ['before execute', 'after execute']); 321 | return done(); 322 | }); 323 | }); 324 | }); 325 | }); 326 | }); 327 | -------------------------------------------------------------------------------- /test/wsdls/calculator_external.wsdl: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Adds two integers. This is a test WebService. ©DNE Online 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /test/wsdls/stockquote.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/wsdls/weather.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Gets Information for each WeatherID 167 | 168 | 169 | 170 | 171 | Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only 172 | 173 | 174 | 175 | 176 | Allows you to get your City's Weather, which is updated hourly. U.S. Only 177 | 178 | 179 | 180 | 181 | 182 | 183 | Gets Information for each WeatherID 184 | 185 | 186 | 187 | 188 | Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only 189 | 190 | 191 | 192 | 193 | Allows you to get your City's Weather, which is updated hourly. U.S. Only 194 | 195 | 196 | 197 | 198 | 199 | 200 | Gets Information for each WeatherID 201 | 202 | 203 | 204 | 205 | Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only 206 | 207 | 208 | 209 | 210 | Allows you to get your City's Weather, which is updated hourly. U.S. Only 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | --------------------------------------------------------------------------------