From c00434bc405d62c3ea41c1fba7431599c6529f0d Mon Sep 17 00:00:00 2001
From: Paul Melnikow
Date: Mon, 18 Feb 2019 22:52:20 -0500
Subject: [PATCH] Validate query params in BaseService [npm nodeping
mozillaobservatory matrix gitlab f-droid endpoint dynamic bitbucket appveyor]
Fix #2676
---
core/base-service/base-svg-scraping.spec.js | 4 ++
core/base-service/base-xml.spec.js | 4 ++
core/base-service/base.js | 63 +++++++++++------
core/base-service/base.spec.js | 70 ++++++++++++++-----
core/base-service/route.js | 12 ++++
core/base-service/route.spec.js | 23 +++++-
services/appveyor/appveyor-tests.service.js | 15 ++--
services/appveyor/appveyor-tests.tester.js | 9 +--
.../bitbucket-pull-request.service.js | 8 ++-
services/dynamic/dynamic-helpers.js | 16 ++---
services/dynamic/dynamic-json.service.js | 11 +--
services/dynamic/dynamic-xml.service.js | 11 +--
services/dynamic/dynamic-yaml.service.js | 11 +--
services/endpoint/endpoint.service.js | 9 +--
services/f-droid/f-droid.service.js | 24 +++----
.../gitlab/gitlab-pipeline-status.service.js | 10 +--
services/matrix/matrix.service.js | 8 +--
.../mozilla-observatory.service.js | 10 ++-
services/nodeping/nodeping-status.service.js | 29 ++++----
services/npm/npm-base.js | 9 ++-
20 files changed, 218 insertions(+), 138 deletions(-)
diff --git a/core/base-service/base-svg-scraping.spec.js b/core/base-service/base-svg-scraping.spec.js
index 2cc8af5c2f8ce..15548e0ba6c90 100644
--- a/core/base-service/base-svg-scraping.spec.js
+++ b/core/base-service/base-svg-scraping.spec.js
@@ -124,6 +124,10 @@ describe('BaseSvgScrapingService', function() {
it('allows overriding the valueMatcher', async function() {
class WithValueMatcher extends BaseSvgScrapingService {
+ static get route() {
+ return {}
+ }
+
async handle() {
return this._requestSvg({
schema,
diff --git a/core/base-service/base-xml.spec.js b/core/base-service/base-xml.spec.js
index 05ac79b59c711..aeba56ee69b64 100644
--- a/core/base-service/base-xml.spec.js
+++ b/core/base-service/base-xml.spec.js
@@ -56,6 +56,10 @@ describe('BaseXmlService', function() {
it('forwards options to _sendAndCacheRequest', async function() {
class WithCustomOptions extends BaseXmlService {
+ static get route() {
+ return {}
+ }
+
async handle() {
const { requiredString } = await this._requestXml({
schema: dummySchema,
diff --git a/core/base-service/base.js b/core/base-service/base.js
index b08b1a3bcb130..5398a1caafac9 100644
--- a/core/base-service/base.js
+++ b/core/base-service/base.js
@@ -18,6 +18,7 @@ const {
assertValidRoute,
prepareRoute,
namedParamsForMatch,
+ getQueryParamNames,
} = require('./route')
const { assertValidServiceDefinition } = require('./service-definitions')
const trace = require('./trace')
@@ -174,7 +175,8 @@ module.exports = class BaseService {
let base, format, pattern, queryParams
try {
- ;({ base, format, pattern, query: queryParams = [] } = this.route)
+ ;({ base, format, pattern } = this.route)
+ queryParams = getQueryParamNames(this.route)
} catch (e) {
// Legacy services do not have a route.
}
@@ -270,12 +272,44 @@ module.exports = class BaseService {
const serviceInstance = new this(context, config)
+ let serviceError
+ const { queryParamSchema } = this.route
+ if (queryParamSchema) {
+ try {
+ queryParams = validate(
+ {
+ ErrorClass: InvalidParameter,
+ prettyErrorMessage: 'invalid query parameter',
+ includeKeys: true,
+ traceErrorMessage: 'Query params did not match schema',
+ traceSuccessMessage: 'Query params after validation',
+ },
+ queryParams,
+ queryParamSchema
+ )
+ trace.logTrace(
+ 'inbound',
+ emojic.crayon,
+ 'Query params after validation',
+ queryParams
+ )
+ } catch (error) {
+ serviceError = error
+ }
+ }
+
let serviceData
- try {
- serviceData = await serviceInstance.handle(namedParams, queryParams)
- Joi.assert(serviceData, serviceDataSchema)
- } catch (error) {
- serviceData = serviceInstance._handleError(error)
+ if (!serviceError) {
+ try {
+ serviceData = await serviceInstance.handle(namedParams, queryParams)
+ Joi.assert(serviceData, serviceDataSchema)
+ } catch (error) {
+ serviceError = error
+ }
+ }
+
+ if (serviceError) {
+ serviceData = serviceInstance._handleError(serviceError)
}
trace.logTrace('outbound', emojic.shield, 'Service data', serviceData)
@@ -286,11 +320,12 @@ module.exports = class BaseService {
static register({ camp, handleRequest, githubApiProvider }, serviceConfig) {
const { cacheHeaders: cacheHeaderConfig, fetchLimitBytes } = serviceConfig
const { regex, captureNames } = prepareRoute(this.route)
+ const queryParams = getQueryParamNames(this.route)
camp.route(
regex,
handleRequest(cacheHeaderConfig, {
- queryParams: this.route.queryParams,
+ queryParams,
handler: async (queryParams, match, sendBadge, request) => {
const namedParams = namedParamsForMatch(captureNames, match, this)
const serviceData = await this.invoke(
@@ -343,20 +378,6 @@ module.exports = class BaseService {
)
}
- static _validateQueryParams(queryParams, queryParamSchema) {
- return validate(
- {
- ErrorClass: InvalidParameter,
- prettyErrorMessage: 'invalid query parameter',
- includeKeys: true,
- traceErrorMessage: 'Query params did not match schema',
- traceSuccessMessage: 'Query params after validation',
- },
- queryParams,
- queryParamSchema
- )
- }
-
async _request({ url, options = {}, errorMessages = {} }) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
logTrace(emojic.bowAndArrow, 'Request', url, '\n', options)
diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js
index a3e4b7fe26201..8bbd754425f2e 100644
--- a/core/base-service/base.spec.js
+++ b/core/base-service/base.spec.js
@@ -16,6 +16,15 @@ const BaseService = require('./base')
require('../register-chai-plugins.spec')
+const queryParamSchema = Joi.object({
+ queryParamA: Joi.string(),
+})
+ .rename('legacyQueryParamA', 'queryParamA', {
+ ignoreUndefined: true,
+ override: true,
+ })
+ .required()
+
class DummyService extends BaseService {
static render({ namedParamA, queryParamA }) {
return {
@@ -62,7 +71,7 @@ class DummyService extends BaseService {
return {
base: 'foo',
pattern: ':namedParamA',
- queryParams: ['queryParamA'],
+ queryParamSchema,
}
}
}
@@ -83,6 +92,21 @@ describe('BaseService', function() {
})
})
+ it('Validates query params', async function() {
+ expect(
+ await DummyService.invoke(
+ {},
+ defaultConfig,
+ { namedParamA: 'bar.bar.bar' },
+ { queryParamA: ['foo', 'bar'] }
+ )
+ ).to.deep.equal({
+ color: 'red',
+ isError: true,
+ message: 'invalid query parameter: queryParamA',
+ })
+ })
+
describe('Required overrides', function() {
it('Should throw if render() is not overridden', function() {
expect(() => BaseService.render()).to.throw(
@@ -90,12 +114,26 @@ describe('BaseService', function() {
)
})
- it('Should throw if handle() is not overridden', async function() {
+ it('Should throw if route is not overridden', async function() {
try {
await BaseService.invoke({}, {}, {})
expect.fail('Expected to throw')
} catch (e) {
- expect(e.message).to.equal('Handler not implemented for BaseService')
+ expect(e.message).to.equal('Route not defined for BaseService')
+ }
+ })
+
+ class WithRoute extends BaseService {
+ static get route() {
+ return {}
+ }
+ }
+ it('Should throw if handle() is not overridden', async function() {
+ try {
+ await WithRoute.invoke({}, {}, {})
+ expect.fail('Expected to throw')
+ } catch (e) {
+ expect(e.message).to.equal('Handler not implemented for WithRoute')
}
})
@@ -139,7 +177,7 @@ describe('BaseService', function() {
expect(trace.logTrace).to.be.calledWith(
'inbound',
sinon.match.string,
- 'Query params',
+ 'Query params after validation',
{ queryParamA: '!' }
)
})
@@ -293,7 +331,15 @@ describe('BaseService', function() {
it('handles the request', async function() {
expect(mockHandleRequest).to.have.been.calledOnce
- const { handler: requestHandler } = mockHandleRequest.getCall(0).args[1]
+
+ const {
+ queryParams: serviceQueryParams,
+ handler: requestHandler,
+ } = mockHandleRequest.getCall(0).args[1]
+ expect(serviceQueryParams).to.deep.equal([
+ 'queryParamA',
+ 'legacyQueryParamA',
+ ])
const mockSendBadge = sinon.spy()
const mockRequest = {
@@ -340,7 +386,7 @@ describe('BaseService', function() {
isDeprecated: false,
route: {
pattern: '/foo/:namedParamA',
- queryParams: [],
+ queryParams: ['queryParamA', 'legacyQueryParamA'],
},
})
@@ -415,18 +461,6 @@ describe('BaseService', function() {
expect(e).to.be.an.instanceof(InvalidResponse)
}
})
-
- it('throws error for invalid query params', async function() {
- try {
- DummyService._validateQueryParams(
- { requiredString: ['this', "shouldn't", 'work'] },
- dummySchema
- )
- expect.fail('Expected to throw')
- } catch (e) {
- expect(e).to.be.an.instanceof(InvalidParameter)
- }
- })
})
describe('request', function() {
diff --git a/core/base-service/route.js b/core/base-service/route.js
index c154db4cdb645..711d7d5ca426b 100644
--- a/core/base-service/route.js
+++ b/core/base-service/route.js
@@ -15,9 +15,11 @@ const routeSchema = Joi.object({
is: Joi.string().required(),
then: Joi.array().items(Joi.string().required()),
}),
+ queryParamSchema: Joi.object().schema(),
queryParams: Joi.array().items(Joi.string().required()),
})
.xor('pattern', 'format')
+ .oxor('queryParamSchema', 'queryParams')
.required()
function assertValidRoute(route, message = undefined) {
@@ -67,9 +69,19 @@ function namedParamsForMatch(captureNames = [], match, ServiceClass) {
return result
}
+function getQueryParamNames({ queryParams = [], queryParamSchema }) {
+ if (queryParamSchema) {
+ const { children, renames = [] } = Joi.describe(queryParamSchema)
+ return Object.keys(children).concat(renames.map(({ from }) => from))
+ } else {
+ return queryParams
+ }
+}
+
module.exports = {
makeFullUrl,
assertValidRoute,
prepareRoute,
namedParamsForMatch,
+ getQueryParamNames,
}
diff --git a/core/base-service/route.spec.js b/core/base-service/route.spec.js
index 9dadb0a196a30..86607976cf715 100644
--- a/core/base-service/route.spec.js
+++ b/core/base-service/route.spec.js
@@ -1,8 +1,13 @@
'use strict'
const { expect } = require('chai')
+const Joi = require('joi')
const { test, given, forCases } = require('sazerac')
-const { prepareRoute, namedParamsForMatch } = require('./route')
+const {
+ prepareRoute,
+ namedParamsForMatch,
+ getQueryParamNames,
+} = require('./route')
describe('Route helpers', function() {
context('A `pattern` with a named param is declared', function() {
@@ -101,4 +106,20 @@ describe('Route helpers', function() {
'Service MyService declares incorrect number of named params (expected 2, got 1)'
)
})
+
+ it('getQueryParamNames', function() {
+ expect(getQueryParamNames({ queryParams: ['foo'] })).to.deep.equal(['foo'])
+ expect(
+ getQueryParamNames({
+ queryParamSchema: Joi.object({ foo: Joi.string() }).required(),
+ })
+ ).to.deep.equal(['foo'])
+ expect(
+ getQueryParamNames({
+ queryParamSchema: Joi.object({ foo: Joi.string() })
+ .rename('bar', 'foo', { ignoreUndefined: true, override: true })
+ .required(),
+ })
+ ).to.deep.equal(['foo', 'bar'])
+ })
})
diff --git a/services/appveyor/appveyor-tests.service.js b/services/appveyor/appveyor-tests.service.js
index af16b606086f0..2b7f5fe740ed2 100644
--- a/services/appveyor/appveyor-tests.service.js
+++ b/services/appveyor/appveyor-tests.service.js
@@ -1,5 +1,6 @@
'use strict'
+const Joi = require('joi')
const { renderTestResultBadge } = require('../../lib/text-formatters')
const AppVeyorBase = require('./appveyor-base')
@@ -19,16 +20,18 @@ const documentation = `
`
+const queryParamSchema = Joi.object({
+ compact_message: Joi.equal(''),
+ passed_label: Joi.string(),
+ failed_label: Joi.string(),
+ skipped_label: Joi.string(),
+}).required()
+
module.exports = class AppVeyorTests extends AppVeyorBase {
static get route() {
return {
...this.buildRoute('appveyor/tests'),
- queryParams: [
- 'compact_message',
- 'passed_label',
- 'failed_label',
- 'skipped_label',
- ],
+ queryParamSchema,
}
}
diff --git a/services/appveyor/appveyor-tests.tester.js b/services/appveyor/appveyor-tests.tester.js
index cfd2fbb6b111a..25acacd4a3ba4 100644
--- a/services/appveyor/appveyor-tests.tester.js
+++ b/services/appveyor/appveyor-tests.tester.js
@@ -1,6 +1,7 @@
'use strict'
const Joi = require('joi')
+const queryString = require('querystring')
const isAppveyorTestTotals = Joi.string().regex(
/^[0-9]+ passed(, [0-9]+ failed)?(, [0-9]+ skipped)?$/
@@ -56,14 +57,14 @@ t.create('Test status with custom labels')
t.create('Test status with compact message and custom labels')
.timeout(10000)
- .get('/NZSmartie/coap-net-iu0to.json', {
- qs: {
+ .get(
+ `/NZSmartie/coap-net-iu0to.json?${queryString.stringify({
compact_message: null,
passed_label: '💃',
failed_label: '🤦♀️',
skipped_label: '🤷',
- },
- })
+ })}`
+ )
.expectJSONTypes(
Joi.object().keys({
name: 'tests',
diff --git a/services/bitbucket/bitbucket-pull-request.service.js b/services/bitbucket/bitbucket-pull-request.service.js
index 496983e5bf8b3..f7561ca85a74f 100644
--- a/services/bitbucket/bitbucket-pull-request.service.js
+++ b/services/bitbucket/bitbucket-pull-request.service.js
@@ -3,13 +3,17 @@
const Joi = require('joi')
const serverSecrets = require('../../lib/server-secrets')
const { metric } = require('../../lib/text-formatters')
-const { nonNegativeInteger } = require('../validators')
+const { nonNegativeInteger, optionalUrl } = require('../validators')
const { BaseJsonService } = require('..')
const bitbucketPullRequestSchema = Joi.object({
size: nonNegativeInteger,
}).required()
+const queryParamSchema = Joi.object({
+ server: optionalUrl,
+}).required()
+
function pullRequestClassGenerator(raw) {
const routePrefix = raw ? 'pr-raw' : 'pr'
const badgeSuffix = raw ? '' : ' open'
@@ -98,7 +102,7 @@ function pullRequestClassGenerator(raw) {
return {
base: `bitbucket/${routePrefix}`,
pattern: `:user/:repo`,
- queryParams: ['server'],
+ queryParamSchema,
}
}
diff --git a/services/dynamic/dynamic-helpers.js b/services/dynamic/dynamic-helpers.js
index e0ab46a2b3df7..d68ccaf487e80 100644
--- a/services/dynamic/dynamic-helpers.js
+++ b/services/dynamic/dynamic-helpers.js
@@ -3,14 +3,6 @@
const Joi = require('joi')
const { optionalUrl } = require('../validators')
-function createRoute(which) {
- return {
- base: `badge/dynamic/${which}`,
- pattern: '',
- queryParams: ['uri', 'url', 'query', 'prefix', 'suffix'],
- }
-}
-
const queryParamSchema = Joi.object({
url: optionalUrl.required(),
query: Joi.string().required(),
@@ -20,6 +12,14 @@ const queryParamSchema = Joi.object({
.rename('uri', 'url', { ignoreUndefined: true, override: true })
.required()
+function createRoute(which) {
+ return {
+ base: `badge/dynamic/${which}`,
+ pattern: '',
+ queryParamSchema,
+ }
+}
+
module.exports = {
createRoute,
queryParamSchema,
diff --git a/services/dynamic/dynamic-json.service.js b/services/dynamic/dynamic-json.service.js
index 55fa9819ec852..abce5fc774d0b 100644
--- a/services/dynamic/dynamic-json.service.js
+++ b/services/dynamic/dynamic-json.service.js
@@ -4,7 +4,7 @@ const Joi = require('joi')
const jp = require('jsonpath')
const { BaseJsonService, InvalidResponse } = require('..')
const { renderDynamicBadge, errorMessages } = require('../dynamic-common')
-const { createRoute, queryParamSchema } = require('./dynamic-helpers')
+const { createRoute } = require('./dynamic-helpers')
module.exports = class DynamicJson extends BaseJsonService {
static get category() {
@@ -21,14 +21,7 @@ module.exports = class DynamicJson extends BaseJsonService {
}
}
- async handle(namedParams, queryParams) {
- const {
- url,
- query: pathExpression,
- prefix,
- suffix,
- } = this.constructor._validateQueryParams(queryParams, queryParamSchema)
-
+ async handle(namedParams, { url, query: pathExpression, prefix, suffix }) {
const data = await this._requestJson({
schema: Joi.any(),
url,
diff --git a/services/dynamic/dynamic-xml.service.js b/services/dynamic/dynamic-xml.service.js
index 1bb2edc65ffd4..189532e774f31 100644
--- a/services/dynamic/dynamic-xml.service.js
+++ b/services/dynamic/dynamic-xml.service.js
@@ -4,7 +4,7 @@ const { DOMParser } = require('xmldom')
const xpath = require('xpath')
const { BaseService, InvalidResponse } = require('..')
const { renderDynamicBadge, errorMessages } = require('../dynamic-common')
-const { createRoute, queryParamSchema } = require('./dynamic-helpers')
+const { createRoute } = require('./dynamic-helpers')
// This service extends BaseService because it uses a different XML parser
// than BaseXmlService which can be used with xpath.
@@ -27,14 +27,7 @@ module.exports = class DynamicXml extends BaseService {
}
}
- async handle(namedParams, queryParams) {
- const {
- url,
- query: pathExpression,
- prefix,
- suffix,
- } = this.constructor._validateQueryParams(queryParams, queryParamSchema)
-
+ async handle(namedParams, { url, query: pathExpression, prefix, suffix }) {
const pathIsAttr = pathExpression.includes('@')
const { buffer } = await this._request({
diff --git a/services/dynamic/dynamic-yaml.service.js b/services/dynamic/dynamic-yaml.service.js
index 6ccc56c4edbdd..800467f29a010 100644
--- a/services/dynamic/dynamic-yaml.service.js
+++ b/services/dynamic/dynamic-yaml.service.js
@@ -4,7 +4,7 @@ const Joi = require('joi')
const jp = require('jsonpath')
const { BaseYamlService, InvalidResponse } = require('..')
const { renderDynamicBadge, errorMessages } = require('../dynamic-common')
-const { createRoute, queryParamSchema } = require('./dynamic-helpers')
+const { createRoute } = require('./dynamic-helpers')
module.exports = class DynamicYaml extends BaseYamlService {
static get category() {
@@ -21,14 +21,7 @@ module.exports = class DynamicYaml extends BaseYamlService {
}
}
- async handle(namedParams, queryParams) {
- const {
- url,
- query: pathExpression,
- prefix,
- suffix,
- } = this.constructor._validateQueryParams(queryParams, queryParamSchema)
-
+ async handle(namedParams, { url, query: pathExpression, prefix, suffix }) {
const data = await this._requestYaml({
schema: Joi.any(),
url,
diff --git a/services/endpoint/endpoint.service.js b/services/endpoint/endpoint.service.js
index 083bc7ef65cf4..79ff34ff11fca 100644
--- a/services/endpoint/endpoint.service.js
+++ b/services/endpoint/endpoint.service.js
@@ -22,7 +22,7 @@ module.exports = class Endpoint extends BaseJsonService {
return {
base: 'badge/endpoint',
pattern: '',
- queryParams: ['url'],
+ queryParamSchema,
}
}
@@ -66,12 +66,7 @@ module.exports = class Endpoint extends BaseJsonService {
}
}
- async handle(namedParams, queryParams) {
- const { url } = this.constructor._validateQueryParams(
- queryParams,
- queryParamSchema
- )
-
+ async handle(namedParams, { url }) {
const { protocol, hostname } = new URL(url)
if (protocol !== 'https:') {
throw new InvalidParameter({ prettyMessage: 'please use https' })
diff --git a/services/f-droid/f-droid.service.js b/services/f-droid/f-droid.service.js
index 046bf92df9eab..3f2b6279c06a2 100644
--- a/services/f-droid/f-droid.service.js
+++ b/services/f-droid/f-droid.service.js
@@ -11,6 +11,10 @@ const schema = Joi.object({
.required(),
}).required()
+const queryParamSchema = Joi.object({
+ metadata_format: Joi.string().valid(['yml', 'txt']),
+}).required()
+
module.exports = class FDroid extends BaseYamlService {
static render({ version }) {
return {
@@ -19,9 +23,7 @@ module.exports = class FDroid extends BaseYamlService {
}
}
- async handle({ appId }, queryParams) {
- const constructor = this.constructor
- const { metadata_format: format } = constructor.validateParams(queryParams)
+ async handle({ appId }, { metadata_format: metadataFormat }) {
const url = `https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/${appId}`
const fetchOpts = {
options: {},
@@ -29,7 +31,7 @@ module.exports = class FDroid extends BaseYamlService {
404: 'app not found',
},
}
- const fetch = format === 'yml' ? this.fetchYaml : this.fetchText
+ const fetch = metadataFormat === 'yml' ? this.fetchYaml : this.fetchText
let result
try {
@@ -38,14 +40,14 @@ module.exports = class FDroid extends BaseYamlService {
// on f-droid, so if txt is not found we look for yml as the fallback
result = await fetch.call(this, url, fetchOpts)
} catch (error) {
- if (format) {
+ if (metadataFormat) {
// if the format was specified it doesn't make the fallback request
throw error
}
result = await this.fetchYaml(url, fetchOpts)
}
- return constructor.render(result)
+ return this.constructor.render(result)
}
async fetchYaml(url, options) {
@@ -82,14 +84,6 @@ module.exports = class FDroid extends BaseYamlService {
return { version: match[1] }
}
- static validateParams(queryParams) {
- const queryParamsSchema = Joi.object({
- metadata_format: Joi.string().valid(['yml', 'txt']),
- }).required()
-
- return this._validateQueryParams(queryParams, queryParamsSchema)
- }
-
// Metadata
static get defaultBadgeData() {
return { label: 'f-droid' }
@@ -103,7 +97,7 @@ module.exports = class FDroid extends BaseYamlService {
return {
base: 'f-droid/v',
pattern: ':appId',
- queryParams: ['metadata_format'],
+ queryParamSchema,
}
}
diff --git a/services/gitlab/gitlab-pipeline-status.service.js b/services/gitlab/gitlab-pipeline-status.service.js
index b1686e75cc2fd..91dedb22c0311 100644
--- a/services/gitlab/gitlab-pipeline-status.service.js
+++ b/services/gitlab/gitlab-pipeline-status.service.js
@@ -27,7 +27,7 @@ module.exports = class GitlabPipelineStatus extends BaseSvgScrapingService {
return {
base: 'gitlab/pipeline',
pattern: ':user/:repo/:branch*',
- queryParams: ['gitlab_url'],
+ queryParamSchema,
}
}
@@ -63,10 +63,10 @@ module.exports = class GitlabPipelineStatus extends BaseSvgScrapingService {
return renderBuildStatusBadge({ status })
}
- async handle({ user, repo, branch = 'master' }, queryParams) {
- const {
- gitlab_url: baseUrl = 'https://gitlab.com',
- } = this.constructor._validateQueryParams(queryParams, queryParamSchema)
+ async handle(
+ { user, repo, branch = 'master' },
+ { gitlab_url: baseUrl = 'https://gitlab.com' }
+ ) {
const { message: status } = await this._requestSvg({
schema: badgeSchema,
url: `${baseUrl}/${user}/${repo}/badges/${branch}/pipeline.svg`,
diff --git a/services/matrix/matrix.service.js b/services/matrix/matrix.service.js
index 17b10bf75c60a..8e910710a73be 100644
--- a/services/matrix/matrix.service.js
+++ b/services/matrix/matrix.service.js
@@ -170,11 +170,7 @@ module.exports = class Matrix extends BaseJsonService {
}
}
- async handle({ roomAlias }, queryParams) {
- const { server_fqdn: serverFQDN } = this.constructor._validateQueryParams(
- queryParams,
- queryParamSchema
- )
+ async handle({ roomAlias }, { server_fqdn: serverFQDN }) {
const members = await this.fetch({ roomAlias, serverFQDN })
return this.constructor.render({ members })
}
@@ -191,7 +187,7 @@ module.exports = class Matrix extends BaseJsonService {
return {
base: 'matrix',
pattern: ':roomAlias',
- queryParams: ['server_fqdn'],
+ queryParamSchema,
}
}
diff --git a/services/mozilla-observatory/mozilla-observatory.service.js b/services/mozilla-observatory/mozilla-observatory.service.js
index f9e0d281157e0..88610e398491f 100644
--- a/services/mozilla-observatory/mozilla-observatory.service.js
+++ b/services/mozilla-observatory/mozilla-observatory.service.js
@@ -1,8 +1,8 @@
'use strict'
+const Joi = require('joi')
const { BaseJsonService } = require('..')
-const Joi = require('joi')
const schema = Joi.object({
state: Joi.string()
.valid('ABORTED', 'FAILED', 'FINISHED', 'PENDING', 'STARTING', 'RUNNING')
@@ -26,6 +26,10 @@ const schema = Joi.object({
.required(),
}).required()
+const queryParamSchema = Joi.object({
+ publish: Joi.equal(''),
+}).required()
+
const documentation = `
The Mozilla HTTP Observatory
@@ -35,7 +39,7 @@ const documentation = `
By default the scan result is hidden from the public result list.
You can activate the publication of the scan result
- by setting publish
parameter to true
+ by setting the publish
parameter.
The badge returns a cached site result if the site has been scanned anytime in the previous 24 hours.
@@ -55,7 +59,7 @@ module.exports = class MozillaObservatory extends BaseJsonService {
return {
base: 'mozilla-observatory',
pattern: ':which(grade|grade-score)/:host',
- queryParams: ['publish'],
+ queryParamSchema,
}
}
diff --git a/services/nodeping/nodeping-status.service.js b/services/nodeping/nodeping-status.service.js
index 987e3fb6dc55e..6c55ee3c7a553 100644
--- a/services/nodeping/nodeping-status.service.js
+++ b/services/nodeping/nodeping-status.service.js
@@ -3,18 +3,23 @@
const { BaseJsonService } = require('..')
const Joi = require('joi')
-const rowSchema = Joi.object().keys({ su: Joi.boolean() })
-
const schema = Joi.array()
- .items(rowSchema)
+ .items(Joi.object().keys({ su: Joi.boolean() }))
.min(1)
+const queryParamSchema = Joi.object({
+ up_message: Joi.string(),
+ down_message: Joi.string(),
+ up_color: Joi.alternatives(Joi.string(), Joi.number()),
+ down_color: Joi.alternatives(Joi.string(), Joi.number()),
+}).required()
+
/*
* this is the checkUuid for the NodePing.com (as used on the [example page](https://nodeping.com/reporting.html#results))
*/
-const sampleCheckUuid = 'jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei'
+const exampleCheckUuid = 'jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei'
-class NodePingStatus extends BaseJsonService {
+module.exports = class NodePingStatus extends BaseJsonService {
static get category() {
return 'monitoring'
}
@@ -29,7 +34,7 @@ class NodePingStatus extends BaseJsonService {
return {
base: 'nodeping/status',
pattern: ':checkUuid',
- queryParams: ['up_message', 'down_message', 'up_color', 'down_color'],
+ queryParamSchema,
}
}
@@ -38,24 +43,24 @@ class NodePingStatus extends BaseJsonService {
{
title: 'NodePing status',
namedParams: {
- checkUuid: sampleCheckUuid,
+ checkUuid: exampleCheckUuid,
},
staticPreview: this.render({ status: true }),
},
{
title: 'NodePing status (customized)',
namedParams: {
- checkUuid: sampleCheckUuid,
+ checkUuid: exampleCheckUuid,
},
queryParams: {
- up_message: 'Online',
+ up_message: 'online',
up_color: 'blue',
- down_message: 'Offline',
+ down_message: 'offline',
down_color: 'lightgrey',
},
staticPreview: this.render({
status: true,
- upMessage: 'Online',
+ upMessage: 'online',
upColor: 'blue',
}),
},
@@ -101,5 +106,3 @@ class NodePingStatus extends BaseJsonService {
: { message: downMessage || 'down', color: downColor || 'red' }
}
}
-
-module.exports = NodePingStatus
diff --git a/services/npm/npm-base.js b/services/npm/npm-base.js
index a6553a143fd67..326455f465f65 100644
--- a/services/npm/npm-base.js
+++ b/services/npm/npm-base.js
@@ -3,6 +3,7 @@
const Joi = require('joi')
const serverSecrets = require('../../lib/server-secrets')
const { BaseJsonService, InvalidResponse, NotFound } = require('..')
+const { optionalUrl } = require('../validators')
const { isDependencyMap } = require('../package-json-helpers')
const deprecatedLicenseObjectSchema = Joi.object({
@@ -30,6 +31,10 @@ const packageDataSchema = Joi.object({
.default([]),
}).required()
+const queryParamSchema = Joi.object({
+ registry_uri: optionalUrl,
+}).required()
+
// Abstract class for NPM badges which display data about the latest version
// of a package.
module.exports = class NpmBase extends BaseJsonService {
@@ -38,13 +43,13 @@ module.exports = class NpmBase extends BaseJsonService {
return {
base,
pattern: ':scope(@[^/]+)?/:packageName/:tag?',
- queryParams: ['registry_uri'],
+ queryParamSchema,
}
} else {
return {
base,
pattern: ':scope(@[^/]+)?/:packageName',
- queryParams: ['registry_uri'],
+ queryParamSchema,
}
}
}