diff --git a/cspell.json b/cspell.json index 62b058a710..cb2afaae1d 100644 --- a/cspell.json +++ b/cspell.json @@ -132,7 +132,8 @@ "webm", "webscript", "Whitespaces", - "xdescribe" + "xdescribe", + "xsrf" ], "dictionaries": [ "html", diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index c736763b8c..63f9ccc4e4 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -20,7 +20,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FlexLayoutModule } from '@angular/flex-layout'; import { ChartsModule } from 'ng2-charts'; -import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TranslateModule } from '@ngx-translate/core'; import { @@ -28,8 +28,7 @@ import { TRANSLATION_PROVIDER, DebugAppConfigService, CoreModule, - CoreAutomationService, - AuthBearerInterceptor + CoreAutomationService } from '@alfresco/adf-core'; import { ExtensionsModule } from '@alfresco/adf-extensions'; import { AppComponent } from './app.component'; @@ -209,10 +208,6 @@ registerLocaleData(localeSv); SearchFilterChipsComponent ], providers: [ - { - provide: HTTP_INTERCEPTORS, useClass: - AuthBearerInterceptor, multi: true - }, { provide: AppConfigService, useClass: DebugAppConfigService }, // not use this service in production { provide: TRANSLATION_PROVIDER, diff --git a/lib/cli/package-lock.json b/lib/cli/package-lock.json index 82eb636d6e..620391762f 100644 --- a/lib/cli/package-lock.json +++ b/lib/cli/package-lock.json @@ -127,12 +127,12 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==" }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "asn1": { "version": "0.2.4", @@ -145,17 +145,17 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { "version": "1.11.0", @@ -170,7 +170,7 @@ "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "requires": { "tweetnacl": "^0.14.3" } @@ -197,7 +197,7 @@ "builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" + "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==" }, "cacache": { "version": "12.0.4", @@ -248,7 +248,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "chalk": { "version": "2.4.2", @@ -276,7 +276,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "combined-stream": { "version": "1.0.8", @@ -299,7 +299,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "concat-stream": { "version": "1.6.2", @@ -367,7 +367,7 @@ "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==" }, "d": { "version": "1.0.1", @@ -381,7 +381,7 @@ "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "requires": { "assert-plus": "^1.0.0" } @@ -397,12 +397,12 @@ "debuglog": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==" }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "dezalgo": { "version": "1.0.3", @@ -456,7 +456,7 @@ "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -486,7 +486,7 @@ "err-code": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" + "integrity": "sha512-CJAN+O0/yA1CKfRn9SXOGctSpEM7DCon/r/5r2eXFMY2zCCJBasFhcM5I+1kh3Ap11FsQCX+vGHceNPvpWKhoA==" }, "es5-ext": { "version": "0.10.62", @@ -516,7 +516,7 @@ "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "requires": { "es6-promise": "^4.0.3" } @@ -533,7 +533,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "event-emitter": { "version": "0.3.5", @@ -567,7 +567,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "fast-deep-equal": { "version": "3.1.3", @@ -630,7 +630,7 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { "version": "3.0.1", @@ -650,7 +650,7 @@ "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -688,7 +688,7 @@ "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", @@ -728,7 +728,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "function-bind": { "version": "1.1.1", @@ -748,7 +748,7 @@ "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "requires": { "assert-plus": "^1.0.0" } @@ -774,7 +774,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" }, "har-validator": { "version": "5.1.5", @@ -796,7 +796,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "has-symbols": { "version": "1.0.3", @@ -833,14 +833,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -869,7 +869,7 @@ "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "requires": { "ms": "^2.0.0" } @@ -885,12 +885,12 @@ "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==" }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, "infer-owner": { "version": "1.0.4", @@ -900,7 +900,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -919,7 +919,7 @@ "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "integrity": "sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==" }, "is-core-module": { "version": "2.2.0", @@ -932,22 +932,22 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, "json-parse-better-errors": { "version": "1.0.2", @@ -972,12 +972,12 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" }, "jsprim": { "version": "1.4.1", @@ -1127,7 +1127,7 @@ "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", "requires": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", @@ -1249,7 +1249,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } @@ -1257,12 +1257,12 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" }, "osenv": { "version": "0.1.5", @@ -1315,7 +1315,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-parse": { "version": "1.0.6", @@ -1325,7 +1325,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "process-nextick-args": { "version": "2.0.1", @@ -1335,12 +1335,12 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" }, "promise-retry": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "integrity": "sha512-StEy2osPr28o17bIW776GtwO6+Q+M9zPiZkYfosciUUMYqjhU/ffwRAH0zN2+uvGyUsn8/YICIHRzLbPacpZGw==", "requires": { "err-code": "^1.0.0", "retry": "^0.10.0" @@ -1397,7 +1397,7 @@ "read-installed": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", - "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", + "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", "requires": { "debuglog": "^1.0.1", "graceful-fs": "^4.1.2", @@ -1450,7 +1450,7 @@ "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", "requires": { "resolve": "^1.1.6" } @@ -1511,7 +1511,7 @@ "retry": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + "integrity": "sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==" }, "rimraf": { "version": "2.7.1", @@ -1524,7 +1524,7 @@ "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", "requires": { "aproba": "^1.1.1" } @@ -1578,7 +1578,7 @@ "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==" }, "smart-buffer": { "version": "4.1.0", @@ -1746,7 +1746,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "through2": { "version": "2.0.5", @@ -1808,7 +1808,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "requires": { "safe-buffer": "^5.0.1" } @@ -1816,7 +1816,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, "type": { "version": "1.2.0", @@ -1826,7 +1826,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typescript": { "version": "3.9.3", @@ -1861,12 +1861,12 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "util-extend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", - "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=" + "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==" }, "uuid": { "version": "3.4.0", @@ -1885,7 +1885,7 @@ "validate-npm-package-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", "requires": { "builtins": "^1.0.3" } @@ -1893,7 +1893,7 @@ "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -1903,7 +1903,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "xtend": { "version": "4.0.2", diff --git a/lib/core/api/alfresco-api/alfresco-api.http-client.spec.ts b/lib/core/api/alfresco-api/alfresco-api.http-client.spec.ts new file mode 100644 index 0000000000..3fc58b7248 --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.http-client.spec.ts @@ -0,0 +1,313 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Emitters, RequestOptions, ResultListDataRepresentationTaskRepresentation, SecurityOptions } from '@alfresco/js-api'; +import { HttpParams } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { AlfrescoApiHttpClient } from './alfresco-api.http-client'; +import { AlfrescoApiResponseError } from './alfresco-api.response-error'; + +const securityOptions: SecurityOptions = { + authentications: {}, + defaultHeaders: {}, + isBpmRequest: false, + enableCsrf: true, + withCredentials: false +}; + +const emitter = { + emit: () => {}, + off: () => {}, + on: () => {}, + once: () => {} +}; + +const emitters: Emitters = { + eventEmitter: emitter, + apiClientEmitter: emitter +}; + +const mockResponse = { + data: [ + { + id: 14, + name: 'nameFake1', + created: '2017-03-01T12:25:17.189+0000' + } + ] +}; + +describe('AlfrescoApiHttpClient', () => { + let angularHttpClient: AlfrescoApiHttpClient; + let controller: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule + ] + }); + angularHttpClient = TestBed.inject(AlfrescoApiHttpClient); + controller = TestBed.inject(HttpTestingController); + }); + + + describe('deserialize', () => { + + afterEach(() => { + controller.verify(); + }); + + it('should deserialize incoming request based on return type', (done) => { + + const options: RequestOptions = { + path: '', + httpMethod: 'POST', + returnType: ResultListDataRepresentationTaskRepresentation, + headerParams: { + 'Content-Type': 'application/json' + }, + accepts: ['application/json'] + }; + + angularHttpClient.request('http://example.com', options, securityOptions, emitters).then((res: ResultListDataRepresentationTaskRepresentation) => { + expect(res instanceof ResultListDataRepresentationTaskRepresentation).toBeTruthy(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(res.data![0].created instanceof Date).toBeTruthy(); + done(); + }); + + const req = controller.expectOne('http://example.com'); + expect(req.request.method).toEqual('POST'); + + req.flush(mockResponse); + + }); + + it('should return parsed json object when responseType is json', (done) => { + + const options: RequestOptions = { + path: '', + httpMethod: 'POST', + responseType: 'json' + }; + + angularHttpClient.request('http://example.com', options, securityOptions, emitters).then((res) => { + expect(res).toEqual(mockResponse); + done(); + }); + + const req = controller.expectOne('http://example.com'); + expect(req.request.method).toEqual('POST'); + + req.flush(mockResponse); + + }); + + it('should emit unauthorized message for 401 request', (done) => { + const options: RequestOptions = { + path: '', + httpMethod: 'POST' + }; + + const spy = spyOn(emitter, 'emit').and.callThrough(); + + angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(() => { + expect(spy).toHaveBeenCalledWith('unauthorized'); + done(); + }); + + const req = controller.expectOne('http://example.com'); + expect(req.request.method).toEqual('POST'); + + req.flush('
', { status: 401, statusText: 'unauthorized'}); + }); + + }); + + describe('upload', () => { + + afterEach(() => { + controller.verify(); + }); + + it('should behave...', () => { + const requestOptions: RequestOptions = { + path: '/nodes/{nodeId}/children', + httpMethod: 'POST', + queryParams: { + autoRename: true, + include: 'allowableOperations', + fields: null + }, + formParams: { + filedata: new File([], 'file.txt'), + relativePath: '', + include: ['allowableOperations'], + renditions: 'doclib', + autoRename: true, + nodeType: 'cm:content' + }, + bodyParam: { + name: 'demo.txt', + nodeType: 'cm:content', + relativePath: '', + newVersion: false, + majorVersion: false, + parentId: '-my-', + path: '' + }, + contentType: 'multipart/form-data', + accept: 'application/json', + returnType: null + }; + + angularHttpClient.request('http://example.com', requestOptions, securityOptions, emitters); + const req = controller.expectOne('http://example.com?autoRename=true&include=allowableOperations'); + expect(req.request.method).toEqual('POST'); + + const body = req.request.body as HttpParams; + + expect(body.get('relativePath')).toBe(''); + expect(body.get('renditions')).toBe('doclib'); + expect(body.get('autoRename')).toBeTruthy(); + expect(body.get('nodeType')).toBe('cm:content'); + expect(body.get('include')).toBe('allowableOperations'); + expect(body.get('filedata')).toEqual(jasmine.any(File)); + + req.flush(''); + }); + }); + + it('should return a Error type on failed promise, for backward compatibility, with string value to prevent JSON.parse from crashing when we try to get status code from message', (done) => { + const options: RequestOptions = { + path: '', + httpMethod: 'POST' + }; + + const errorResponse = { + error: { + errorKey: 'Cant perform action', + statusCode: 403 + } + }; + + angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch((res: AlfrescoApiResponseError) => { + expect(res instanceof Error).toBeTruthy(); + expect(res.message).toBe(JSON.stringify(errorResponse)); + expect(res.status).toBe(403); + done(); + }); + + const req = controller.expectOne('http://example.com'); + expect(req.request.method).toEqual('POST'); + + req.flush(errorResponse, { status: 403, statusText: 'Forbidden' }); + }); + + it('should return a Error type on failed promise with response body', (done) => { + const options: RequestOptions = { + path: '', + httpMethod: 'POST', + responseType: 'blob' + }; + + const errorResponse = new Blob(); + + angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch((res: AlfrescoApiResponseError) => { + expect(res.status).toBe(400); + expect(res.error.response.body instanceof Blob).toBeTruthy(); + done(); + }); + + const req = controller.expectOne('http://example.com'); + + req.flush(errorResponse, { status: 400, statusText: 'Bad request' }); + }); + + it('should correctly handle queryParams with arrays', () => { + const options: RequestOptions = { + path: '', + httpMethod: 'POST', + queryParams: { + skipCount: 0, + status: [ + 'RUNNING', + 'SUSPENDED' + ], + sort: 'startDate,DESC' + } + }; + + angularHttpClient.request('http://example.com/candidatebaseapp/query/v1/process-instances', options, securityOptions, emitters); + + const req = controller.expectOne('http://example.com/candidatebaseapp/query/v1/process-instances?skipCount=0&status=RUNNING&status=SUSPENDED&sort=startDate%2CDESC'); + expect(req.request.method).toEqual('POST'); + + req.flush(null, { status: 200, statusText: 'Ok' }); + }); + + it('should convert null values to empty stirng for backward compatibility', (done) => { + const options: RequestOptions = { + path: '', + httpMethod: 'GET' + }; + + angularHttpClient.request('http://example.com', options, securityOptions, emitters).then((res) => { + expect(res).toEqual(''); + done(); + }); + + const req = controller.expectOne('http://example.com'); + + req.flush(null, { status: 200, statusText: 'Ok' }); + }); + + it('should correctly decode types to string', () => { + const options: RequestOptions = { + path: '', + httpMethod: 'POST', + queryParams: { + lastModifiedFrom: '2022-08-17T00:00:00.000+02:00' + } + }; + + angularHttpClient.request('http://example.com', options, securityOptions, emitters); + + const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000%2B02%3A00'); + + req.flush(null, { status: 200, statusText: 'Ok' }); + }); + + it('should correctly decode Date types to string ', () => { + const options: RequestOptions = { + path: '', + httpMethod: 'POST', + queryParams: { + lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z') + } + }; + + angularHttpClient.request('http://example.com', options, securityOptions, emitters); + + const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z'); + + req.flush(null, { status: 200, statusText: 'Ok' }); + }); + +}); diff --git a/lib/core/api/alfresco-api/alfresco-api.http-client.ts b/lib/core/api/alfresco-api/alfresco-api.http-client.ts new file mode 100644 index 0000000000..4abf3cbcbd --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.http-client.ts @@ -0,0 +1,224 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Emitters as JsApiEmitters, HttpClient as JsApiHttpClient, RequestOptions, SecurityOptions, isBrowser } from '@alfresco/js-api'; +import { HttpClient, HttpErrorResponse, HttpEvent, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, of, Subject, throwError } from 'rxjs'; +import { catchError, map, takeUntil } from 'rxjs/operators'; +import { convertObjectToFormData, getQueryParamsWithCustomEncoder, isBlobResponse, isConstructor, isHttpResponseEvent, isHttpUploadProgressEvent, removeNilValues } from './alfresco-api.utils'; +import { AlfrescoApiParamEncoder } from './alfresco-api.param-encoder'; +import { AlfrescoApiResponseError } from './alfresco-api.response-error'; +import { Constructor } from '../types'; + +@Injectable({ + providedIn: 'root' +}) +export class AlfrescoApiHttpClient implements JsApiHttpClient { + + constructor(private httpClient: HttpClient) {} + + request(url: string, options: RequestOptions, sc: SecurityOptions, emitters: JsApiEmitters): Promise { + const body = AlfrescoApiHttpClient.getBody(options); + const params = getQueryParamsWithCustomEncoder(options.queryParams, new AlfrescoApiParamEncoder()); + const headers = AlfrescoApiHttpClient.getHeaders(options); + const responseType = AlfrescoApiHttpClient.getResponseType(options); + + const request = this.httpClient.request( + options.httpMethod, + url, + { + ...(body && { body }), + ...(responseType && { responseType }), + ...(sc.withCredentials && { withCredentials: true }), + ...(params && { params }), + headers, + observe: 'events', + reportProgress: true + } + ); + + return this.requestWithLegacyEventEmitters(request, emitters, options.returnType); + } + + post(url: string, options: RequestOptions, sc: SecurityOptions, emitters: JsApiEmitters): Promise { + return this.request(url, { ...options, httpMethod: 'POST' }, sc, emitters); + } + + put(url: string, options: RequestOptions, sc: SecurityOptions, emitters: JsApiEmitters): Promise { + return this.request(url, { ...options, httpMethod: 'PUT' }, sc, emitters); + } + + get(url: string, options: RequestOptions, sc: SecurityOptions, emitters: JsApiEmitters): Promise { + return this.request(url, { ...options, httpMethod: 'GET' }, sc, emitters); + } + + delete(url: string, options: RequestOptions, sc: SecurityOptions, emitters: JsApiEmitters): Promise { + return this.request(url, { ...options, httpMethod: 'DELETE' }, sc, emitters); + } + + private requestWithLegacyEventEmitters(request$: Observable>, emitters: JsApiEmitters, returnType: any): Promise { + + const abort$ = new Subject(); + const { eventEmitter, apiClientEmitter } = emitters; + + const promise = request$.pipe( + map((res) => { + if (isHttpUploadProgressEvent(res)) { + const percent = Math.round((res.loaded / res.total) * 100); + eventEmitter.emit('progress', { loaded: res.loaded, total: res.total, percent }); + } + + if (isHttpResponseEvent(res)) { + eventEmitter.emit('success', res.body); + return AlfrescoApiHttpClient.deserialize(res, returnType); + } + }), + catchError((err: HttpErrorResponse): Observable => { + + // since we can't always determinate ahead of time if the response is going to be xml or plain text response + // we need to handle false positive cases here. + + if (err.status === 200) { + eventEmitter.emit('success', err.error.text); + return of(err.error.text); + } + + eventEmitter.emit('error', err); + apiClientEmitter.emit('error', err); + + if (err.status === 401) { + eventEmitter.emit('unauthorized'); + apiClientEmitter.emit('unauthorized'); + } + + // for backwards compatibility we need to convert it to error class as the HttpErrorResponse only implements Error interface, not extending it, + // and we need to be able to correctly pass instanceof Error conditions used inside repository + // we also need to pass error as Stringify string as we are detecting statusCodes using JSON.parse(error.message) in some places + const msg = typeof err.error === 'string' ? err.error : JSON.stringify(err.error); + + // for backwards compatibility to handle cases in code where we try read response.error.response.body; + + const error = { + response: { ...err, body: err.error } + }; + + const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error); + + return throwError(alfrescoApiError); + }), + takeUntil(abort$) + ).toPromise(); + + (promise as any).abort = function() { + eventEmitter.emit('abort'); + abort$.next(); + abort$.complete(); + return this; + }; + + return promise; + } + + private static getBody(options: RequestOptions): any { + const contentType = options.contentType; + const isFormData = contentType === 'multipart/form-data'; + const isFormUrlEncoded = contentType === 'application/x-www-form-urlencoded'; + const body = options.bodyParam; + + if (isFormData) { + return convertObjectToFormData(options.formParams); + } + + if (isFormUrlEncoded) { + return new HttpParams({ fromObject: removeNilValues(options.formParams) }); + } + + return body; + } + + private static getHeaders(options: RequestOptions): HttpHeaders { + const optionsHeaders = { + ...options.headerParams, + ...(options.accept && { Accept: options.accept }), + ...((options.contentType) && { 'Content-Type': options.contentType }) + }; + + return new HttpHeaders(optionsHeaders); + } + + private static getResponseType(options: RequestOptions): 'blob' | 'json' | 'text' { + + const isBlobType = options.returnType?.toString().toLowerCase() === 'blob' || options.responseType?.toString().toLowerCase() === 'blob'; + + if (isBlobType) { + return 'blob'; + } + + if (options.returnType === 'String') { + return 'text'; + } + + return 'json'; + } + + /** + * Deserialize an HTTP response body into a value of the specified type. + */ + private static deserialize(response: HttpResponse, returnType?: Constructor | 'blob'): any { + + if (response === null) { + return null; + } + + const body = response.body; + + if (!returnType) { + // for backwards compatibility we need to return empty string instead of null, + // to avoid issues when accessing null response would break application [C309878] + // cannot read property 'entry' of null in cases like + // return this.post(apiUrl, saveFormRepresentation).pipe(map((res: any) => res.entry)) + + return body !== null ? body : ''; + } + + if (isBlobResponse(response, returnType)) { + return AlfrescoApiHttpClient.deserializeBlobResponse(response); + } + + if (!isConstructor(returnType)) { + return body; + } + + if (Array.isArray(body)) { + return body.map((element) => new returnType(element)); + } + + return new returnType(body); + } + + + private static deserializeBlobResponse(response: HttpResponse) { + + if (isBrowser()) { + return new Blob([response.body], { type: response.headers.get('Content-Type') }); + } + + return Buffer.from(response.body as unknown as WithImplicitCoercion, 'binary'); + } +} + diff --git a/lib/core/api/alfresco-api/alfresco-api.param-encoder.spec.ts b/lib/core/api/alfresco-api/alfresco-api.param-encoder.spec.ts new file mode 100644 index 0000000000..9c6d3c4db8 --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.param-encoder.spec.ts @@ -0,0 +1,29 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlfrescoApiParamEncoder } from './alfresco-api.param-encoder'; + +describe('AlfrescoApiParamEncoder', () => { + it('should propely encode special "+" character', () => { + const encoder = new AlfrescoApiParamEncoder(); + const value = '2022-08-17T00:00:00.000+02:00'; + const encodeValue = '2022-08-17T00%3A00%3A00.000%2B02%3A00'; + + expect(encoder.encodeValue(value)).toBe(encodeValue); + expect(encoder.decodeValue(encodeValue)).toBe(value); + }); +}); diff --git a/lib/core/api/alfresco-api/alfresco-api.param-encoder.ts b/lib/core/api/alfresco-api/alfresco-api.param-encoder.ts new file mode 100644 index 0000000000..a53df6a731 --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.param-encoder.ts @@ -0,0 +1,40 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HttpParameterCodec } from '@angular/common/http'; + +// The default implementation of HttpParameterCodec from angular +// does not encode some special characters like + with is causing issues with the alfresco js API and returns 500 error + +export class AlfrescoApiParamEncoder implements HttpParameterCodec { + + encodeKey(key: string): string { + return encodeURIComponent(key); + } + + encodeValue(value: string): string { + return encodeURIComponent(value); + } + + decodeKey(key: string): string { + return decodeURIComponent(key); + } + + decodeValue(value: string): string { + return decodeURIComponent(value); + } +} diff --git a/lib/core/api/alfresco-api/alfresco-api.response-error.ts b/lib/core/api/alfresco-api/alfresco-api.response-error.ts new file mode 100644 index 0000000000..02aedffc18 --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.response-error.ts @@ -0,0 +1,25 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class AlfrescoApiResponseError extends Error { + + public name = 'AlfrescoApiResponseError'; + + constructor(msg: string, public status: number, public error: { response: Record }) { + super(msg); + } +} diff --git a/lib/core/api/alfresco-api/alfresco-api.utils.spec.ts b/lib/core/api/alfresco-api/alfresco-api.utils.spec.ts new file mode 100644 index 0000000000..bf882d1388 --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.utils.spec.ts @@ -0,0 +1,99 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isConstructor, getQueryParamsWithCustomEncoder, removeNilValues } from './alfresco-api.utils'; + +describe('AlfrescoApiUtils', () => { + + describe('isConstructor', () => { + class MockClass {} + function mockFUnction() {} + + it('should return true for class and functions', () => { + expect(isConstructor(MockClass)).toBe(true); + expect(isConstructor(mockFUnction)).toBe(true); + }); + + it('should return false for instances of a class/function', () => { + expect(isConstructor(new MockClass())).toBe(false); + expect(isConstructor(new mockFUnction())).toBe(false); + }); + + it('should return false for object', () => { + expect(isConstructor({})).toBe(false); + }); + + it('should return false for primitive types', () => { + expect(isConstructor('test')).toBe(false); + expect(isConstructor(1)).toBe(false); + expect(isConstructor(true)).toBe(false); + expect(isConstructor(false)).toBe(false); + expect(isConstructor(null)).toBe(false); + expect(isConstructor(undefined)).toBe(false); + }); + }); + + + describe('getQueryParamsWithCustomEncoder', () => { + + it('should return queryParams with removed undefined values', () => { + const actual = getQueryParamsWithCustomEncoder({ + key1: 'value1', + key2: undefined + }); + + expect(actual?.has('key2')).toBe(false); + }); + + it('should handle array values', () => { + const actual = getQueryParamsWithCustomEncoder({ + key1: 'value1', + key2: [undefined, 'value2', null, 'value3', ''] + }); + + expect(actual?.get('key2')).toEqual('value2'); + expect(actual?.getAll('key2')).toEqual(['value2', 'value3']); + }); + }); + + + describe('removeUndefinedValues', () => { + + it('should return queryParams with removed undefined values', () => { + const actual = removeNilValues({ + key1: 'value1', + key2: undefined, + key3: null + }); + + expect(actual).toEqual({ + key1: 'value1' + }); + }); + + it('should handle array values', () => { + const actual = getQueryParamsWithCustomEncoder({ + key1: 'value1', + key2: [undefined, 'value2', null, 'value3', ''] + }); + + expect(actual?.get('key2')).toEqual('value2'); + expect(actual?.getAll('key2')).toEqual(['value2', 'value3']); + }); + }); + +}); diff --git a/lib/core/api/alfresco-api/alfresco-api.utils.ts b/lib/core/api/alfresco-api/alfresco-api.utils.ts new file mode 100644 index 0000000000..e6f0275b65 --- /dev/null +++ b/lib/core/api/alfresco-api/alfresco-api.utils.ts @@ -0,0 +1,91 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HttpEvent, HttpUploadProgressEvent, HttpEventType, HttpResponse, HttpParams, HttpParameterCodec, HttpUrlEncodingCodec } from '@angular/common/http'; +import { Constructor } from '../types'; + +export const isHttpUploadProgressEvent = (val: HttpEvent): val is HttpUploadProgressEvent => val.type === HttpEventType.UploadProgress; +export const isHttpResponseEvent = (val: HttpEvent): val is HttpResponse => val.type === HttpEventType.Response; +export const isDate = (value: unknown): value is Date => value instanceof Date; +export const isXML = (value: unknown): boolean => typeof value === 'string' && value.startsWith(', returnType: Constructor | 'blob'): response is HttpResponse => returnType === 'blob' || response.body instanceof Blob; +export const isConstructor = (value: any): value is Constructor => typeof value === 'function' && !!value?.prototype?.constructor.name; + +const convertParamsToString = (value: any): any => isDate(value) ? value.toISOString() : value; +export const getQueryParamsWithCustomEncoder = (obj: Record, encoder: HttpParameterCodec = new HttpUrlEncodingCodec()): HttpParams | undefined => { + if (!obj) { + return undefined; + } + + let httpParams = new HttpParams({ + encoder + }); + + const params = removeNilValues(obj); + + for (const key in params) { + + if (Object.prototype.hasOwnProperty.call(params, key)) { + const value = params[key]; + if (value instanceof Array) { + const array = value.map(convertParamsToString).filter(Boolean); + httpParams = httpParams.appendAll({ + [key]: array + }); + } else { + httpParams = httpParams.append(key, convertParamsToString(value)); + } + } + } + + return httpParams; +}; + +/** + * Removes null and undefined values from an object. + */ +export const removeNilValues = (obj: Record) => { + + if (!obj) { + return {}; + } + + return Object.keys(obj).reduce((acc, key) => { + const value = obj[key]; + const isNil = value === undefined || value === null; + return isNil ? acc : { ...acc, [key]: value }; + }, {}); +}; + + +export const convertObjectToFormData = (formParams: Record): FormData => { + + const formData = new FormData(); + + for (const key in formParams) { + if (Object.prototype.hasOwnProperty.call(formParams, key)) { + const value = formParams[key]; + if (value instanceof File) { + formData.append(key, value, value.name); + } else { + formData.append(key, value); + } + } + } + + return formData; +}; diff --git a/lib/core/api/public-api.ts b/lib/core/api/public-api.ts index c905317914..3281ffa843 100644 --- a/lib/core/api/public-api.ts +++ b/lib/core/api/public-api.ts @@ -19,3 +19,4 @@ export * from './api-client.factory'; export * from './api-clients.service'; export * from './clients'; export * from './types'; +export * from './alfresco-api/alfresco-api.http-client'; diff --git a/lib/core/src/lib/core.module.ts b/lib/core/src/lib/core.module.ts index d46f5ccc50..508b800265 100644 --- a/lib/core/src/lib/core.module.ts +++ b/lib/core/src/lib/core.module.ts @@ -63,6 +63,8 @@ import { VersionCompatibilityService } from './services/version-compatibility.se import { AlfrescoJsClientsModule } from '@alfresco/adf-core/api'; import { LegacyApiClientModule } from './api-factories/legacy-api-client.module'; import { RichTextEditorModule } from './rich-text-editor/rich-text-editor.module'; +import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { AuthBearerInterceptor } from './services/auth-bearer.interceptor'; @NgModule({ imports: [ @@ -101,7 +103,12 @@ import { RichTextEditorModule } from './rich-text-editor/rich-text-editor.module BlankPageModule, LegacyApiClientModule, AlfrescoJsClientsModule, - RichTextEditorModule + RichTextEditorModule, + HttpClientModule, + HttpClientXsrfModule.withOptions({ + cookieName: 'CSRF-TOKEN', + headerName: 'X-CSRF-TOKEN' + }) ], exports: [ AboutModule, @@ -167,7 +174,8 @@ export class CoreModule { useFactory: versionCompatibilityFactory, deps: [ VersionCompatibilityService ], multi: true - } + }, + { provide: HTTP_INTERCEPTORS, useClass: AuthBearerInterceptor, multi: true } ] }; } diff --git a/lib/core/src/lib/services/auth-bearer.interceptor.ts b/lib/core/src/lib/services/auth-bearer.interceptor.ts index 9c82c2eecc..3a763cea9c 100644 --- a/lib/core/src/lib/services/auth-bearer.interceptor.ts +++ b/lib/core/src/lib/services/auth-bearer.interceptor.ts @@ -27,14 +27,13 @@ import { catchError, mergeMap } from 'rxjs/operators'; @Injectable() export class AuthBearerInterceptor implements HttpInterceptor { private excludedUrlsRegex: RegExp[]; - private authService: AuthenticationService; - constructor(private injector: Injector) { } + constructor(private injector: Injector, private authService: AuthenticationService) { } private loadExcludedUrlsRegex() { const excludedUrls: string[] = this.authService.getBearerExcludedUrls(); - this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(urlPattern, 'gi')) || []; + this.excludedUrlsRegex = [...excludedUrls].map((urlPattern) => new RegExp(urlPattern, 'i')) || []; } intercept(req: HttpRequest, next: HttpHandler): @@ -51,7 +50,7 @@ export class AuthBearerInterceptor implements HttpInterceptor { } const urlRequest = req.url; - const shallPass: boolean = !!this.excludedUrlsRegex.find((regex) => regex.test(urlRequest)); + const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(urlRequest)); if (shallPass) { return next.handle(req) .pipe( @@ -73,7 +72,19 @@ export class AuthBearerInterceptor implements HttpInterceptor { } private appendJsonContentType(headers: HttpHeaders): HttpHeaders { - return headers.set('Content-Type', 'application/json;charset=UTF-8'); + + // prevent adding any content type, to properly handle formData with boundary browser generated value, + // as adding any Content-Type its going to break the upload functionality + + if (headers.get('Content-Type') === 'multipart/form-data') { + return headers.delete('Content-Type'); + } + + if (!headers.get('Content-Type')) { + return headers.set('Content-Type', 'application/json;charset=UTF-8'); + } + + return headers; } } diff --git a/lib/core/src/lib/services/authentication.service.ts b/lib/core/src/lib/services/authentication.service.ts index a6ace14636..2f8277286c 100644 --- a/lib/core/src/lib/services/authentication.service.ts +++ b/lib/core/src/lib/services/authentication.service.ts @@ -246,7 +246,7 @@ export class AuthenticationService { * @returns The ticket or `null` if none was found */ getTicketEcm(): string | null { - return this.alfrescoApi.getInstance().getTicketEcm(); + return this.alfrescoApi.getInstance()?.getTicketEcm(); } /** @@ -255,7 +255,7 @@ export class AuthenticationService { * @returns The ticket or `null` if none was found */ getTicketBpm(): string | null { - return this.alfrescoApi.getInstance().getTicketBpm(); + return this.alfrescoApi.getInstance()?.getTicketBpm(); } /** @@ -264,7 +264,7 @@ export class AuthenticationService { * @returns The ticket or `null` if none was found */ getTicketEcmBase64(): string | null { - const ticket = this.alfrescoApi.getInstance().getTicketEcm(); + const ticket = this.alfrescoApi.getInstance()?.getTicketEcm(); if (ticket) { return 'Basic ' + btoa(ticket); } @@ -395,13 +395,47 @@ export class AuthenticationService { headers = new HttpHeaders(); } try { - const token: string = this.getToken(); - headers = headers.set('Authorization', 'bearer ' + token); - observer.next(headers); + const header = this.getAuthHeaders(headers); + + observer.next(header); observer.complete(); } catch (error) { observer.error(error); } }); } + + private getAuthHeaders(header: HttpHeaders): HttpHeaders { + const authType = this.appConfig.get(AppConfigValues.AUTHTYPE, 'BASIC'); + + switch (authType) { + case 'OAUTH': + return this.addBearerToken(header); + case 'BASIC': + return this.addBasicAuth(header); + default: + return header; + } + } + + private addBearerToken(header: HttpHeaders): HttpHeaders { + const token: string = this.getToken(); + + if (!token) { + return header; + } + + return header.set('Authorization', 'bearer ' + token); + } + + private addBasicAuth(header: HttpHeaders): HttpHeaders { + const ticket: string = this.getTicketEcmBase64(); + + if (!ticket) { + return header; + } + + return header.set('Authorization', ticket); + } + }