@kalisio/kdk 2.6.2 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,16 @@ export class Authentication extends AuthenticationService {
30
30
  return {}
31
31
  }
32
32
  }
33
+
34
+ // Similarly the subject will not be set by Feathers for stateless tokens that do not target existing users.
35
+ async getTokenOptions (authResult, params) {
36
+ const jwtOptions = await super.getTokenOptions(authResult, params)
37
+ if (!jwtOptions.subject) {
38
+ const subject = _.get(authResult, 'authentication.payload.sub')
39
+ if (subject) jwtOptions.subject = subject
40
+ }
41
+ return jwtOptions
42
+ }
33
43
  }
34
44
 
35
45
  export class AuthenticationProviderStrategy extends OAuthStrategy {
@@ -105,6 +115,7 @@ export class JWTAuthenticationStrategy extends JWTStrategy {
105
115
  const { identityFields } = authConfig
106
116
  const { entity } = this.configuration
107
117
  const renewJwt = _.get(this.configuration, 'renewJwt', true)
118
+ const { provider, ...paramsWithoutProvider } = params
108
119
 
109
120
  if (!accessToken) {
110
121
  throw new NotAuthenticated('No access token')
@@ -142,7 +153,8 @@ export class JWTAuthenticationStrategy extends JWTStrategy {
142
153
  $or: _.reduce(identityFields, (or, field) => or.concat([{ [field]: payload.sub }]), []),
143
154
  $limit: 1
144
155
  }
145
- const response = await this.entityService.find({ ...params, query })
156
+ // Avoid sending provider to internal call as it might raises some issues with hooks relying on it otherwise
157
+ const response = await this.entityService.find({ ...paramsWithoutProvider, query })
146
158
  const [value = null] = response.data ? response.data : response
147
159
  // Otherwise assume a stateless token
148
160
  if (value) {
@@ -1,10 +1,12 @@
1
1
  import _ from 'lodash'
2
2
  import { marshallComparisonFields, marshallTime, marshallBooleanFields, marshallNumberFields, marshallDateFields } from '../marshall.js'
3
3
  import mongodb from 'mongodb'
4
+ import errors from '@feathersjs/errors'
4
5
  import makeDebug from 'debug'
5
6
  import { makeDiacriticPattern } from '../../common/utils.js'
6
7
 
7
8
  const { ObjectID } = mongodb
9
+ const { Forbidden } = errors
8
10
  const debug = makeDebug('kdk:core:query:hooks')
9
11
 
10
12
  export function marshallTimeQuery (hook) {
@@ -59,10 +61,17 @@ export function marshallHttpQuery (hook) {
59
61
  }
60
62
 
61
63
  export async function aggregationQuery (hook) {
64
+ if (hook.type !== 'before') {
65
+ throw new Error('The \'aggregationQuery\' hook should only be used as a \'before\' hook.')
66
+ }
62
67
  const query = hook.params.query
63
68
  if (!query) return
64
69
  const service = hook.service
65
70
  if (query.$aggregation) {
71
+ // Generic aggregation request could allow to disclose or update information in DB so that it should only be used through controlled internal calls
72
+ if (hook.params.provider) {
73
+ throw new Forbidden('You are not allowed to perform aggregation')
74
+ }
66
75
  const collection = service.Model
67
76
  // Set result to avoid service DB call
68
77
  hook.result = await collection.aggregate(query.$aggregation.pipeline, query.$aggregation.options).toArray()
@@ -17,7 +17,12 @@ export const Capabilities = {
17
17
  const capabilities = await window.fetch(api.getConfig('domain') + _.get(config, 'apiPath') + '/capabilities')
18
18
  this.content = await capabilities.json()
19
19
  // Store latest capabilities data for offline mode
20
- await LocalCache.setItem('capabilities', this.content)
20
+ // Avoid blocking on eg QuotaExceededError
21
+ try {
22
+ await LocalCache.setItem('capabilities', this.content)
23
+ } catch (error) {
24
+ logger.error(error)
25
+ }
21
26
  }
22
27
  logger.debug('[KDK] Capabilities initialized with content:', this.content)
23
28
  if (!this.content) return
@@ -2,7 +2,7 @@
2
2
  <q-chip
3
3
  v-model="computedState"
4
4
  :selected="selected"
5
- :icon="icon"
5
+ :icon="computedIcon"
6
6
  :iconRight="iconRight"
7
7
  :iconRemove="iconRemove"
8
8
  :iconSelected="iconSelected"
@@ -17,7 +17,7 @@
17
17
  @click="event => emit('click', event)"
18
18
  class="k-chip"
19
19
  >
20
- <div v-if="computedLabel"
20
+ <div v-if="!hideLabel && computedLabel"
21
21
  :id="id"
22
22
  class="ellipsis"
23
23
  :class="{ 'q-pl-sm': !dense && (icon || isTruncated ) , 'q-pl-xs': dense && (icon || isTruncated) }"
@@ -46,10 +46,21 @@ const props = defineProps({
46
46
  type: [String, Number],
47
47
  default: ''
48
48
  },
49
+ hideLabel: {
50
+ type: Boolean,
51
+ default: false
52
+ },
49
53
  tooltip: {
50
54
  type: String,
51
55
  default: undefined
52
56
  },
57
+ tooltipBehavior: {
58
+ type: String,
59
+ default: 'truncated',
60
+ validator: (value) => {
61
+ return ['always', 'truncated', 'never'].includes(value)
62
+ }
63
+ },
53
64
  icon: {
54
65
  type: String,
55
66
  default: undefined
@@ -66,6 +77,10 @@ const props = defineProps({
66
77
  type: String,
67
78
  default: 'check'
68
79
  },
80
+ hideIcon: {
81
+ type: Boolean,
82
+ default: false
83
+ },
69
84
  modelValue: {
70
85
  type: Boolean,
71
86
  default: true
@@ -129,11 +144,17 @@ const computedState = computed({
129
144
  }
130
145
  })
131
146
  const computedLabel = computed(() => {
132
- return i18n.tie(props.label)
147
+ if (!props.hideLabel && props.label) return i18n.tie(props.label)
148
+ })
149
+ const computedIcon = computed(() => {
150
+ if (!props.hideIcon) return props.icon
133
151
  })
134
152
  const computedTooltip = computed(() => {
135
- if (props.tooltip) return i18n.tie(props.tooltip)
136
- if (props.label && isTruncated.value) return computedLabel.value
153
+ if (props.tooltipBehavior === 'never') return
154
+ if (props.tooltipBehavior === 'truncated') {
155
+ if (!isTruncated.value) return
156
+ }
157
+ return props.label ? i18n.tie(props.label) : props.tooltip
137
158
  })
138
159
  const computedColor = computed(() => {
139
160
  return props.outline ? 'transparent' : getHtmlColor(props.color)
@@ -153,7 +174,7 @@ const computedBorderColor = computed(() => {
153
174
  function onResize () {
154
175
  // check whether the label is truncated
155
176
  const chipElement = document.getElementById(id)
156
- if (chipElement) isTruncated.value = (chipElement && chipElement.offsetWidth < chipElement.scrollWidth)
177
+ if (chipElement) isTruncated.value = (chipElement && (chipElement.offsetWidth < chipElement.scrollWidth))
157
178
  }
158
179
  </script>
159
180
 
@@ -163,10 +163,10 @@ export default {
163
163
  computedSections () {
164
164
  return _.map(this.sections, (value, key) => {
165
165
  return {
166
- ...value,
167
166
  item: this.item,
168
167
  actions: _.filter(this.itemActions, { scope: key }),
169
- dense: this.dense
168
+ dense: this.dense,
169
+ ...value
170
170
  }
171
171
  })
172
172
  },
@@ -11,7 +11,11 @@ async function authenticate(authentication) {
11
11
  let user = Store.get('user')
12
12
  if (user) return
13
13
  // Store latest authentication data for offline mode
14
- await LocalCache.setItem('authentication', authentication)
14
+ try {
15
+ await LocalCache.setItem('authentication', authentication)
16
+ } catch (error) {
17
+ logger.error(error)
18
+ }
15
19
  // Anonymous user or user account ?
16
20
  user = authentication.user ? authentication.user : { name: i18n.t('composables.ANONYMOUS'), anonymous: true }
17
21
  Store.set('user', user)
@@ -105,6 +105,15 @@ module.exports = {
105
105
  }
106
106
  }, options)
107
107
  },
108
+ toggleZoomControl: (options) => {
109
+ return Object.assign({
110
+ target: '#toggle-zoom-control-sticky',
111
+ title: 'tours.navigation-bar.ZOOM_CONTROL_LABEL',
112
+ params: {
113
+ placement: 'bottom'
114
+ }
115
+ }, options)
116
+ },
108
117
  positionIndicator: (options) => {
109
118
  return Object.assign({
110
119
  target: '#position-indicator',
@@ -1,8 +1,10 @@
1
1
  import _ from 'lodash'
2
+ import errors from '@feathersjs/errors'
2
3
  import { marshallGeometry } from '../marshall.js'
3
4
  import makeDebug from 'debug'
4
5
 
5
6
  const debug = makeDebug('kdk:map:query:hooks')
7
+ const { Forbidden } = errors
6
8
 
7
9
  export function marshallGeometryQuery (hook) {
8
10
  const query = hook.params.query
@@ -238,6 +240,62 @@ export function asGeoJson (options = {}) {
238
240
  }
239
241
  }
240
242
 
243
+
244
+
245
+ // Verifies that only authorized accumulation operators are used
246
+ function validateGroupExpression(expression) {
247
+ // Safe accumulation operators for $group
248
+ const SAFE_GROUP_ACCUMULATORS = new Set([
249
+ '$sum', '$avg', '$first', '$last', '$max', '$min'
250
+ ])
251
+ // Safe expression operators usable in $group
252
+ const SAFE_GROUP_EXPRESSIONS = new Set([
253
+ // Arithmetic operators
254
+ '$add', '$subtract', '$multiply', '$divide', '$mod',
255
+ '$abs', '$ceil', '$floor', '$round', '$trunc',
256
+ '$sqrt', '$pow', '$exp', '$ln', '$log', '$log10',
257
+ // Comparison operators
258
+ '$eq', '$ne', '$gt', '$gte', '$lt', '$lte', '$cmp',
259
+ // Logical operators
260
+ '$and', '$or', '$not',
261
+ // Date operators
262
+ '$dateToString', '$year', '$month', '$dayOfMonth', '$hour',
263
+ '$minute', '$second', '$dayOfWeek', '$dayOfYear', '$week',
264
+ // Conditional operators
265
+ '$cond', '$ifNull', '$switch',
266
+ // Type operators
267
+ '$toString', '$toInt', '$toDouble'
268
+ ])
269
+ // Dangerous operators to reject in $group (arbitrary code execution)
270
+ const DANGEROUS_GROUP_OPERATORS = new Set([
271
+ '$accumulator', // Custom JavaScript execution
272
+ '$function', // JavaScript function execution
273
+ '$where' // JavaScript code execution
274
+ ])
275
+
276
+ if (_.isNil(expression)) {
277
+ return
278
+ } else if (Array.isArray(expression)) {
279
+ expression.forEach(validateGroupExpression)
280
+ return
281
+ } else if (typeof expression === 'object') {
282
+ for (const [key, value] of Object.entries(expression)) {
283
+ // Check if it's an operator
284
+ if (key.startsWith('$')) {
285
+ if (DANGEROUS_GROUP_OPERATORS.has(key)) throw new Forbidden(`You are not allowed to use ${key} operator`)
286
+
287
+ // Check if operator is in the allowed list
288
+ const isAccumulator = SAFE_GROUP_ACCUMULATORS.has(key)
289
+ const isExpression = SAFE_GROUP_EXPRESSIONS.has(key)
290
+ if (!isAccumulator && !isExpression) throw new Forbidden(`You are not allowed to use ${key} operator`)
291
+ }
292
+
293
+ // Recursively validate the value
294
+ validateGroupExpression(value)
295
+ }
296
+ }
297
+ }
298
+
241
299
  export async function aggregateFeaturesQuery (hook) {
242
300
  const query = hook.params.query
243
301
  const service = hook.service
@@ -294,6 +352,8 @@ export async function aggregateFeaturesQuery (hook) {
294
352
  }
295
353
  // Merge with any additional group expression
296
354
  const group = _.get(query, '$group', {})
355
+ // Check for possible injection
356
+ validateGroupExpression(group)
297
357
  Object.assign(groupBy, group)
298
358
  // The query contains the match stage except options relevent to the aggregation pipeline
299
359
  const match = _.omit(query, ['$group', '$groupBy', '$aggregate', '$geoNear', '$sort', '$limit', '$skip'])
@@ -4,8 +4,8 @@
4
4
  id="zoom-out"
5
5
  icon="remove"
6
6
  tooltip="mixins.activity.ZOOM_OUT"
7
- color="white"
8
- text-color="grey-9"
7
+ :color="color"
8
+ :text-color="textColor"
9
9
  :flat="square"
10
10
  round
11
11
  @click="onZoomOutFn"
@@ -17,8 +17,8 @@
17
17
  id="zoom-in"
18
18
  icon="add"
19
19
  tooltip="mixins.activity.ZOOM_IN"
20
- color="white"
21
- text-color="grey-9"
20
+ :color="color"
21
+ :text-color="textColor"
22
22
  :flat="square"
23
23
  round
24
24
  @click="onZoomInFn"
@@ -33,16 +33,24 @@ import { composables as kCoreComposables } from '@kalisio/kdk/core.client'
33
33
  // Props
34
34
  defineProps({
35
35
  vertical: {
36
- default: true,
37
- type: Boolean
36
+ type: Boolean,
37
+ default: true
38
38
  },
39
39
  square: {
40
- default: false,
41
- type: Boolean
40
+ type: Boolean,
41
+ default: false
42
42
  },
43
43
  size: {
44
+ type: String,
44
45
  default: '10px',
45
- type: String
46
+ },
47
+ color: {
48
+ type: String,
49
+ default: 'white'
50
+ },
51
+ textColor: {
52
+ type: String,
53
+ default: 'grey-9'
46
54
  }
47
55
  })
48
56
 
@@ -114,6 +114,12 @@
114
114
  "PRINT_VIEW": "Print view",
115
115
  "REMOVE_LABEL": "Remove"
116
116
  },
117
+ "gesture": {
118
+ "TOUCH": "Use two fingers to move the map",
119
+ "SCROLL": "Use Ctrl + scroll to zoom the map",
120
+ "SCROLL_MAC": "Use ⌘ + scroll to zoom the map",
121
+ "CTRL": "Use Ctrl + scroll to zoom the map"
122
+ },
117
123
  "timeseries": {
118
124
  "FORECAST_PROBE": "Forecast"
119
125
  },
@@ -312,7 +318,8 @@
312
318
  "TOGGLE_VR_LABEL": "Switch to Virtual Reality (VR) mode with this button.",
313
319
  "TOGGLE_FULLSCREEN_LABEL": "Switch to fullscreen mode with this button.",
314
320
  "TOGGLE_CATALOG_LABEL": "Open the catalog with this button.",
315
- "NORTH_ARROW_LABEL": "Display or hide the north arrow using this button."
321
+ "NORTH_ARROW_LABEL": "Display or hide the north arrow using this button.",
322
+ "ZOOM_CONTROL_LABEL": "Display or hide the zoom control buttons using this button."
316
323
  },
317
324
  "catalog-panel": {
318
325
  "CATALOG_LABEL": "The <b>catalog</b> allows to manage data displayed on your map.<br/>The catalogd is divided into 3 different tabs.",
@@ -114,6 +114,12 @@
114
114
  "PRINT_VIEW": "Imprimer la vue",
115
115
  "REMOVE_LABEL": "Supprimer"
116
116
  },
117
+ "gesture": {
118
+ "TOUCH": "Utilisez deux doigts pour déplacer la carte",
119
+ "SCROLL": "Utilisez Ctrl + molette pour zoomer",
120
+ "SCROLL_MAC": "Utilisez ⌘ + molette pour zoomer",
121
+ "CTRL": "Utilisez Ctrl + molette pour zoomer"
122
+ },
117
123
  "timeseries": {
118
124
  "FORECAST_PROBE": "Prévisions"
119
125
  },
@@ -312,7 +318,8 @@
312
318
  "TOGGLE_VR_LABEL": "Basculez en mode <b>Réalité Virtuelle</b> (VR) avec ce bouton.",
313
319
  "TOGGLE_FULLSCREEN_LABEL": "Basculez en mode plein écran avec ce bouton.",
314
320
  "TOGGLE_CATALOG_LABEL": "Ouvrez le <b>catalogue</b> avec ce bouton.",
315
- "NORTH_ARROW_LABEL": "Affichez ou cachez la flèche nord avec ce bouton."
321
+ "NORTH_ARROW_LABEL": "Affichez ou cachez la flèche nord avec ce bouton.",
322
+ "ZOOM_CONTROL_LABEL": "Affichez ou cachez les boutons de zoom avec ce bouton."
316
323
  },
317
324
  "catalog-panel": {
318
325
  "CATALOG_LABEL": "Le <b>catalogue</b> permet de gérer les données affichées sur votre carte.<br/>Le catalogue se décompose en 3 onglets.",
@@ -3,6 +3,8 @@ import sift from 'sift'
3
3
  import logger from 'loglevel'
4
4
  import moment from 'moment'
5
5
  import { EventBus } from 'quasar'
6
+ import { Platform } from '../../../../core/client/platform.js'
7
+ import { i18n } from '../../../../core/client/i18n.js'
6
8
  import { point, rhumbDistance, rhumbBearing, rhumbDestination } from '@turf/turf'
7
9
  import L from 'leaflet'
8
10
  import 'leaflet/dist/leaflet.css'
@@ -24,6 +26,8 @@ import 'leaflet-timedimension/dist/leaflet.timedimension.control.css'
24
26
  import '@geoman-io/leaflet-geoman-free'
25
27
  import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css'
26
28
  import 'leaflet-rotate/dist/leaflet-rotate-src.js'
29
+ import 'leaflet-gesture-handling'
30
+ import 'leaflet-gesture-handling/dist/leaflet-gesture-handling.css'
27
31
 
28
32
  import { Time } from '../../../../core/client/time.js'
29
33
  import { Events } from '../../../../core/client/events.js'
@@ -108,8 +112,22 @@ export const baseMap = {
108
112
  geolocate: true,
109
113
  rotateControl: false // Rotate plugin show this even if rotation is disabled
110
114
  })
115
+ const gestureHandlingOptions = {
116
+ text: {
117
+ touch: i18n.t('mixins.gesture.TOUCH'),
118
+ scroll: i18n.t('mixins.gesture.SCROLL'),
119
+ scrollMac: i18n.t('mixins.gesture.SCROLL_MAC'),
120
+ ctrl: i18n.t('mixins.gesture.CTRL')
121
+ },
122
+ duration: 1000
123
+ }
111
124
  // Initialize the map
112
- this.map = L.map(domEl, Object.assign({ zoomControl: false }, viewerOptions))
125
+ this.map = L.map(domEl, Object.assign({
126
+ zoomControl: false,
127
+ touchZoom: true,
128
+ gestureHandling: Platform.has.touch && Platform.is.desktop && !Platform.is.firefox,
129
+ gestureHandlingOptions
130
+ }, viewerOptions))
113
131
  const backgroundColor = _.get(viewerOptions, 'backgroundColor')
114
132
  if (backgroundColor) this.map._container.style.backgroundColor = backgroundColor
115
133
  // Make sure geoman is initialized on the map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kalisio/kdk",
3
3
  "description": "Kalisio Development Kit",
4
- "version": "2.6.2",
4
+ "version": "2.6.4",
5
5
  "homepage": "https://github.com/kalisio/kdk",
6
6
  "type": "module",
7
7
  "keywords": [
@@ -94,7 +94,7 @@
94
94
  "@feathersjs/feathers": "^5.0.8",
95
95
  "@feathersjs/schema": "^5.0.8",
96
96
  "@feathersjs/socketio": "^5.0.8",
97
- "@kalisio/feathers-import-export": "^1.3.2",
97
+ "@kalisio/feathers-import-export": "^1.4.0",
98
98
  "@kalisio/feathers-s3": "^1.5.0",
99
99
  "@kalisio/feathers-webpush": "^1.0.2",
100
100
  "@turf/bbox": "^6.0.1",
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - added `get_toml_value` to extract values from TOML fields
12
13
  - support for mongo 8 (`install_mongo8`)
13
14
  - added `get_flavor_from_git_ref` `get_version_from_git_ref` `get_custom_from_git_ref` helpers to parse git ref names (tag or branch names).
14
15
  - added `install_sona_scanner_cli` to install SonarQube scanner cli tool
@@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
19
  - support for mongo 4,5,6
19
20
  - helper to install k9s
20
21
  - support for node 16,18
22
+ - support for Code Climate
21
23
 
22
24
  ### Changed
23
25
 
@@ -32,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
32
34
  - node22 bumped to `22.16`
33
35
  - mongodb7 bumped to `7.0.21`
34
36
  - mongodb8 bumped to `8.0.10`
37
+ - allow `_` character in custom fields (see `get_custom_from_git_ref`)
38
+ - run_app_tests now run lint on whole project repo (using `yarn lint`)
35
39
 
36
40
  ### Fixed
37
41
 
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017-20xx Kalisio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -2,3 +2,12 @@
2
2
  Kalisio's bash snippets repository
3
3
 
4
4
  ## HOWTO use
5
+
6
+
7
+ ## License
8
+
9
+ Licensed under the [MIT license](LICENSE).
10
+
11
+ Copyright (c) 2017-present [Kalisio](https://kalisio.com)
12
+
13
+ [![Kalisio](https://kalisio.github.io/kalisioscope/kalisio/kalisio-logo-black-256x84.png)](https://kalisio.com)
@@ -129,11 +129,11 @@ AGE_VERSION=1.1.1
129
129
  SOPS_VERSION=3.8.1
130
130
 
131
131
  # https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG
132
- KUBECTL_VERSION=1.28.13
132
+ KUBECTL_VERSION=1.31.11
133
133
  # https://github.com/helm/helm/releases
134
- HELM_VERSION=3.14.4
134
+ HELM_VERSION=3.18.4
135
135
  # https://github.com/helmfile/helmfile/releases
136
- HELMFILE_VERSION=0.167.1
136
+ HELMFILE_VERSION=1.1.3
137
137
 
138
138
  # https://github.com/nvm-sh/nvm/releases
139
139
  NVM_VERSION=0.40.3
@@ -246,34 +246,6 @@ ensure_sops() {
246
246
  fi
247
247
  }
248
248
 
249
- # Install code climate test reporter in ~/.local/bin
250
- # Arg1: a writable folder where to write downloaded files
251
- install_cc_test_reporter() {
252
- local DL_ROOT=$1
253
- local DL_PATH="$DL_ROOT/cc"
254
- if [ ! -d "$DL_PATH" ]; then
255
- mkdir -p "$DL_PATH" && cd "$DL_PATH"
256
- curl -OLsS https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64
257
- curl -OLsS https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64.sha256
258
- sha256sum --ignore-missing --quiet -c test-reporter-latest-linux-amd64.sha256
259
- cd ~-
260
- fi
261
- cd "$DL_PATH"
262
- cp test-reporter-latest-linux-amd64 ~/.local/bin/cc-test-reporter
263
- chmod +x ~/.local/bin/cc-test-reporter
264
- cd ~-
265
- }
266
-
267
- # Sends test coverage to code climate
268
- # Arg1: code climate identifier for authentication
269
- # Arg2: prefix to use when using format-coverage (can be empty)
270
- send_coverage_to_cc() {
271
- local CC_TEST_REPORTER_ID=$1
272
- local CC_PREFIX=${2:-}
273
- ~/.local/bin/cc-test-reporter format-coverage -t lcov --add-prefix "$CC_PREFIX" coverage/lcov.info
274
- ~/.local/bin/cc-test-reporter upload-coverage -r "$CC_TEST_REPORTER_ID"
275
- }
276
-
277
249
  # Make sure nvm is installed
278
250
  # Arg1: a writable folder where to write downloaded files
279
251
  # NOTE: also define 'yarn' as a default package, ie. it'll be automatically
@@ -486,12 +458,28 @@ use_mongo() {
486
458
  ### Utils
487
459
  ###
488
460
 
461
+ # Extract a value from a TOML file
462
+ # Expected args:
463
+ # 1. the toml file
464
+ # 2. the field to extract
465
+ get_toml_value() {
466
+ local TOML_SRC="$1"
467
+ local TOML_FIELD="$2"
468
+
469
+ ensure_yq
470
+ yq --input-format=toml --output-format=yaml ".$TOML_FIELD" "$TOML_SRC"
471
+ }
472
+
473
+ # Extract a value from a JSON file
474
+ # Expected args:
475
+ # 1. the toml file
476
+ # 2. the field to extract
489
477
  get_json_value() {
490
478
  local JSON_SRC="$1"
491
479
  local JSON_FIELD="$2"
492
480
 
493
481
  ensure_yq
494
- yq --output-format=yaml ".$JSON_FIELD" "$JSON_SRC"
482
+ yq --input-format=yaml --output-format=yaml ".$JSON_FIELD" "$JSON_SRC"
495
483
  }
496
484
 
497
485
  # Extract version major from a version string.
@@ -1003,7 +991,7 @@ get_version_from_git_ref() {
1003
991
  get_custom_from_git_ref() {
1004
992
  local GIT_REF=$1
1005
993
 
1006
- local CUSTOM_REGEX="(^|-)([a-zA-Z0-9]+)$"
994
+ local CUSTOM_REGEX="(^|-)([a-zA-Z0-9_]+)$"
1007
995
  if [[ "$GIT_REF" =~ $CUSTOM_REGEX ]]; then
1008
996
  if [[ "${BASH_REMATCH[1]}" == "" ]]; then
1009
997
  # If first capture group is empty => that's probably a 'dev' flavor.
@@ -1284,13 +1272,13 @@ get_app_kli_file() {
1284
1272
  # Expected arguments:
1285
1273
  # 1. the app repository directory
1286
1274
  # 2. the directory in which we'll find kli files relative to the 'development' repository root directory
1287
- # 3. wether to publish code coverage results (boolean)
1275
+ # 3. whether to run SonarQube analysis and publish code quality & coverage results (boolean)
1288
1276
  # 4. the node version to use (16, 18, ...)
1289
1277
  # 5. the mongo version to use (5, 6, ...). Mongo will not be started if not provided
1290
1278
  run_app_tests() {
1291
1279
  local REPO_DIR="$1"
1292
1280
  local KLI_BASE="$2"
1293
- local CODE_COVERAGE="$3"
1281
+ local RUN_SONAR="$3"
1294
1282
  local NODE_VER="$4"
1295
1283
  local MONGO_VER="$5"
1296
1284
  local WORKSPACE_DIR
@@ -1307,6 +1295,13 @@ run_app_tests() {
1307
1295
 
1308
1296
  echo "About to run tests for $APP v$VERSION-$FLAVOR ..."
1309
1297
 
1298
+ ## Lint whole project
1299
+ ##
1300
+
1301
+ cd "$REPO_DIR"
1302
+ yarn lint
1303
+ cd ~-
1304
+
1310
1305
  ## Start mongo
1311
1306
  ##
1312
1307
 
@@ -1322,19 +1317,19 @@ run_app_tests() {
1322
1317
  ## Run tests
1323
1318
  ##
1324
1319
 
1325
- pushd "$REPO_DIR/api"
1320
+ cd "$REPO_DIR/api"
1326
1321
 
1327
1322
  use_node "$NODE_VER"
1328
1323
  yarn test
1329
1324
 
1330
- ## Publish code coverage
1325
+ ## Run SonarQube analysis and publish code quality & coverage reports
1331
1326
  ##
1332
1327
 
1333
- if [ "$CODE_COVERAGE" = true ]; then
1334
- send_coverage_to_cc "$CC_TEST_REPORTER_ID" "api"
1328
+ if [ "$RUN_SONAR" = true ]; then
1329
+ cd "$ROOT_DIR" && sonar-scanner
1335
1330
  fi
1336
1331
 
1337
- popd
1332
+ cd ~-
1338
1333
  }
1339
1334
 
1340
1335
  # Setup the workspace for a lib project.
@@ -1397,12 +1392,12 @@ get_lib_branch() {
1397
1392
  # Run tests for a library module
1398
1393
  # Expected arguments
1399
1394
  # 1. Root directory
1400
- # 2. true to publish code coverage to code climate (CC_TEST_REPORTER_ID env var should be defined in this case)
1395
+ # 2. whether to run SonarQube analysis and publish code quality & coverage results (boolean)
1401
1396
  # 3. node version to be used
1402
1397
  # 4. mongo version to be used if required by tests
1403
1398
  run_lib_tests () {
1404
1399
  local ROOT_DIR="$1"
1405
- local CODE_COVERAGE="$2"
1400
+ local RUN_SONAR="$2"
1406
1401
  local NODE_VER="$3"
1407
1402
  local MONGO_VER="$4"
1408
1403
  local WORKSPACE_DIR
@@ -1437,11 +1432,11 @@ run_lib_tests () {
1437
1432
  use_node "$NODE_VER"
1438
1433
  yarn && yarn test
1439
1434
 
1440
- ## Publish code coverage
1435
+ ## Run SonarQube analysis and publish code quality & coverage reports
1441
1436
  ##
1442
1437
 
1443
- if [ "$CODE_COVERAGE" = true ]; then
1444
- send_coverage_to_cc "$CC_TEST_REPORTER_ID"
1438
+ if [ "$RUN_SONAR" = true ]; then
1439
+ cd "$ROOT_DIR" && sonar-scanner
1445
1440
  fi
1446
1441
  }
1447
1442
 
@@ -11,7 +11,7 @@ ROOT_DIR=$(dirname "$THIS_DIR")
11
11
  ### Github Actions
12
12
 
13
13
  init_github() {
14
- install_reqs yq age sops nvm node20 node22 cc_test_reporter sonar_scanner_cli
14
+ install_reqs yq age sops nvm node20 node22 sonar_scanner_cli
15
15
 
16
16
  # mongo is not available for alpine hosts
17
17
  if [ "$OS_ID" != "alpine" ]; then
@@ -49,6 +49,8 @@ mkdir -p "$TMP_DIR/utils"
49
49
  cd "$TMP_DIR/utils"
50
50
  curl -OLsS "https://raw.githubusercontent.com/kalisio/krawler/master/package.json"
51
51
  [ "$(get_json_value "$TMP_DIR/utils/package.json" 'name')" != "@kalisio/krawler" ] && exit 1
52
+ curl -OLsS "https://raw.githubusercontent.com/kalisio/kazarr/refs/heads/master/pyproject.toml"
53
+ [ "$(get_toml_value "$TMP_DIR/utils/pyproject.toml" 'project.name')" != "kazarr" ] && exit 1
52
54
  cd ~-
53
55
 
54
56
  [ "$(get_semver_major "1" )" != "1" ] && exit 1
@@ -129,6 +131,7 @@ git_shallow_clone https://github.com/kalisio/kApp.git "$TMP_DIR/kApp.v1.3.0" pro
129
131
  [[ "$(get_custom_from_git_ref "test-v4.3-blabla")" != "blabla" ]] && exit 1
130
132
  [[ "$(get_custom_from_git_ref "prod-v4.5.3")" != "" ]] && exit 1
131
133
  [[ "$(get_custom_from_git_ref "prod-v4.5.3-custom")" != "custom" ]] && exit 1
134
+ [[ "$(get_custom_from_git_ref "prod-v4.5.3-custom_foo")" != "custom_foo" ]] && exit 1
132
135
 
133
136
  # Setup a fake workspace with additional dependencies
134
137
  mkdir -p "$TMP_DIR/fake"
@@ -83,6 +83,7 @@ module.exports = {
83
83
  bucket: process.env.S3_BUCKET,
84
84
  prefix: 'import-export'
85
85
  },
86
+ allowedServicePaths: 'api/users',
86
87
  workingDir: 'test/api/core/tmp'
87
88
  },
88
89
  mailer: {
@@ -402,6 +402,27 @@ describe('map:services', () => {
402
402
  $aggregate: ['H']
403
403
  }
404
404
 
405
+ it('performs unauthorised element aggregation on vigicrues observations service', async () => {
406
+ try {
407
+ await vigicruesObsService.find({
408
+ query: Object.assign({ $sort: { time: -1 }, $limit: 1, $group: { property: { $accumulator: { lang: 'js' } } } }, aggregationQuery)
409
+ })
410
+ } catch (error) {
411
+ expect(error).toExist()
412
+ expect(error.name).to.equal('Forbidden')
413
+ }
414
+ try {
415
+ await vigicruesObsService.find({
416
+ query: Object.assign({ $sort: { time: -1 }, $limit: 1, $group: { property: { $sum: { $function: { lang: 'js' } } } } }, aggregationQuery)
417
+ })
418
+ } catch (error) {
419
+ expect(error).toExist()
420
+ expect(error.name).to.equal('Forbidden')
421
+ }
422
+ })
423
+ // Let enough time to process
424
+ .timeout(5000)
425
+
405
426
  it('performs element aggregation on vigicrues observations service', async () => {
406
427
  const result = await vigicruesObsService.find({
407
428
  query: Object.assign({}, aggregationQuery)