From bd6f4ee1465d14a8f188c37823748a21b6a46762 Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Sun, 25 Jul 2021 17:53:41 +0200 Subject: [PATCH] fix: authenticate weblate requests (#6790) Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com> --- app.json | 4 ++++ config/custom-environment-variables.yml | 3 +++ config/default.yml | 2 ++ .../local-shields-io-production.template.yml | 1 + config/local.template.yml | 1 + core/server/server.js | 2 ++ doc/server-secrets.md | 15 +++++++++++++ services/weblate/weblate-base.js | 21 +++++++++++++++++++ .../weblate-component-license.service.js | 14 +++++-------- services/weblate/weblate-entities.service.js | 15 ++++++------- ...e-project-translated-percentage.service.js | 14 +++++-------- .../weblate/weblate-user-statistic.service.js | 15 ++++++------- 12 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 services/weblate/weblate-base.js diff --git a/app.json b/app.json index d0ed5429ff..ad5f008a5d 100644 --- a/app.json +++ b/app.json @@ -31,6 +31,10 @@ "TWITCH_CLIENT_SECRET": { "description": "Configure the client secret to be used for the Twitch service.", "required": false + }, + "WEBLATE_API_KEY": { + "description": "Configure the API key to be used for the Weblate service.", + "required": false } }, "formation": { diff --git a/config/custom-environment-variables.yml b/config/custom-environment-variables.yml index 18de1c353c..d84ba4f948 100644 --- a/config/custom-environment-variables.yml +++ b/config/custom-environment-variables.yml @@ -52,6 +52,8 @@ public: authorizedOrigins: 'SONAR_ORIGINS' teamcity: authorizedOrigins: 'TEAMCITY_ORIGINS' + weblate: + authorizedOrigins: 'WEBLATE_ORIGINS' trace: 'TRACE_SERVICES' cacheHeaders: @@ -95,4 +97,5 @@ private: wheelmap_token: 'WHEELMAP_TOKEN' influx_username: 'INFLUX_USERNAME' influx_password: 'INFLUX_PASSWORD' + weblate_api_key: 'WEBLATE_API_KEY' youtube_api_key: 'YOUTUBE_API_KEY' diff --git a/config/default.yml b/config/default.yml index 32d1e09c98..973de38132 100644 --- a/config/default.yml +++ b/config/default.yml @@ -22,6 +22,8 @@ public: debug: enabled: false intervalSeconds: 200 + weblate: + authorizedOrigins: 'https://hosted.weblate.org' trace: false cacheHeaders: diff --git a/config/local-shields-io-production.template.yml b/config/local-shields-io-production.template.yml index 1c0594234d..f127e05fcf 100644 --- a/config/local-shields-io-production.template.yml +++ b/config/local-shields-io-production.template.yml @@ -10,5 +10,6 @@ private: sl_insight_apiToken: ... twitch_client_id: ... twitch_client_secret: ... + weblate_api_key: ... wheelmap_token: ... youtube_api_key: ... diff --git a/config/local.template.yml b/config/local.template.yml index 1a1e12a473..0ceadf8f53 100644 --- a/config/local.template.yml +++ b/config/local.template.yml @@ -7,5 +7,6 @@ private: gh_token: '...' twitch_client_id: '...' twitch_client_secret: '...' + weblate_api_key: '...' wheelmap_token: '...' youtube_api_key: '...' diff --git a/core/server/server.js b/core/server/server.js index 4dbcf4c232..f63408b256 100644 --- a/core/server/server.js +++ b/core/server/server.js @@ -135,6 +135,7 @@ const publicConfigSchema = Joi.object({ npm: defaultService, sonar: defaultService, teamcity: defaultService, + weblate: defaultService, trace: Joi.boolean().required(), }).required(), cacheHeaders: { defaultCacheLengthSeconds: nonNegativeInteger }, @@ -182,6 +183,7 @@ const privateConfigSchema = Joi.object({ wheelmap_token: Joi.string(), influx_username: Joi.string(), influx_password: Joi.string(), + weblate_api_key: Joi.string(), youtube_api_key: Joi.string(), }).required() const privateMetricsInfluxConfigSchema = privateConfigSchema.append({ diff --git a/doc/server-secrets.md b/doc/server-secrets.md index 091e1786ff..9d3422fa5d 100644 --- a/doc/server-secrets.md +++ b/doc/server-secrets.md @@ -219,6 +219,21 @@ access to your private nexus repositories. Register an application in the [Twitch developer console](https://dev.twitch.tv/console) in order to obtain a client id and a client secret for making Twitch API calls. +### Weblate + +- `WEBLATE_ORIGINS` (yml: `public.services.weblate.authorizedOrigins`) +- `WEBLATE_API_KEY` (yml: `private.weblate_api_key`) + +By default Weblate throttles [unauthenticated request][weblate authentication] +to only 100 requests per day, after this you will need an API key or else +badges will stop working. + +You can find your Weblate API key in your profile under +["API access"][weblate api key location]. + +[weblate authentication]: https://docs.weblate.org/en/latest/api.html#authentication-and-generic-parameters +[weblate api key location]: https://hosted.weblate.org/accounts/profile/#api + ### Wheelmap - `WHEELMAP_TOKEN` (yml: `private.wheelmap_token`) diff --git a/services/weblate/weblate-base.js b/services/weblate/weblate-base.js new file mode 100644 index 0000000000..12aed7c901 --- /dev/null +++ b/services/weblate/weblate-base.js @@ -0,0 +1,21 @@ +import Joi from 'joi' +import { BaseJsonService } from '../index.js' +import { optionalUrl } from '../validators.js' + +export default class WeblateBase extends BaseJsonService { + static queryParamSchema = Joi.object({ + server: optionalUrl, + }).required() + + static auth = { + passKey: 'weblate_api_key', + isRequired: false, + serviceKey: 'weblate', + } + + async fetch(requestParams) { + return this._requestJson( + this.authHelper.withBearerAuthHeader(requestParams, 'Token') + ) + } +} diff --git a/services/weblate/weblate-component-license.service.js b/services/weblate/weblate-component-license.service.js index 2e4e97cc90..1de097624c 100644 --- a/services/weblate/weblate-component-license.service.js +++ b/services/weblate/weblate-component-license.service.js @@ -1,24 +1,20 @@ import Joi from 'joi' -import { BaseJsonService } from '../index.js' -import { optionalUrl } from '../validators.js' +import WeblateBase from './weblate-base.js' const schema = Joi.object({ license: Joi.string().required(), }).required() -const queryParamSchema = Joi.object({ - server: optionalUrl, -}).required() - /** * This badge displays the license of a component on a Weblate instance. */ -export default class WeblateComponentLicense extends BaseJsonService { +export default class WeblateComponentLicense extends WeblateBase { static category = 'license' + static route = { base: 'weblate/l', pattern: ':project/:component', - queryParamSchema, + queryParamSchema: this.queryParamSchema, } static examples = [ @@ -38,7 +34,7 @@ export default class WeblateComponentLicense extends BaseJsonService { } async fetch({ project, component, server = 'https://hosted.weblate.org' }) { - return this._requestJson({ + return super.fetch({ schema, url: `${server}/api/components/${project}/${component}/`, errorMessages: { diff --git a/services/weblate/weblate-entities.service.js b/services/weblate/weblate-entities.service.js index a0013502ac..df5c53d9ca 100644 --- a/services/weblate/weblate-entities.service.js +++ b/services/weblate/weblate-entities.service.js @@ -1,22 +1,19 @@ import Joi from 'joi' -import { BaseJsonService } from '../index.js' -import { nonNegativeInteger, optionalUrl } from '../validators.js' +import { nonNegativeInteger } from '../validators.js' import { metric } from '../text-formatters.js' +import WeblateBase from './weblate-base.js' const schema = Joi.object({ count: nonNegativeInteger, }).required() -const queryParamSchema = Joi.object({ - server: optionalUrl, -}).required() - -export default class WeblateEntities extends BaseJsonService { +export default class WeblateEntities extends WeblateBase { static category = 'other' + static route = { base: 'weblate', pattern: ':type(components|projects|users|languages)', - queryParamSchema, + queryParamSchema: this.queryParamSchema, } static examples = [ @@ -36,7 +33,7 @@ export default class WeblateEntities extends BaseJsonService { } async fetch({ type, server = 'https://hosted.weblate.org' }) { - return this._requestJson({ + return super.fetch({ schema, url: `${server}/api/${type}/`, errorMessages: { diff --git a/services/weblate/weblate-project-translated-percentage.service.js b/services/weblate/weblate-project-translated-percentage.service.js index 8eb77e2bb2..b4a2481ed0 100644 --- a/services/weblate/weblate-project-translated-percentage.service.js +++ b/services/weblate/weblate-project-translated-percentage.service.js @@ -1,26 +1,22 @@ import Joi from 'joi' -import { BaseJsonService } from '../index.js' -import { optionalUrl } from '../validators.js' import { colorScale } from '../color-formatters.js' +import WeblateBase from './weblate-base.js' const schema = Joi.object({ translated_percent: Joi.number().required(), }).required() -const queryParamSchema = Joi.object({ - server: optionalUrl, -}).required() - /** * This badge displays the percentage of strings translated on a project on a * Weblate instance. */ -export default class WeblateProjectTranslatedPercentage extends BaseJsonService { +export default class WeblateProjectTranslatedPercentage extends WeblateBase { static category = 'other' + static route = { base: 'weblate/progress', pattern: ':project', - queryParamSchema, + queryParamSchema: this.queryParamSchema, } static examples = [ @@ -50,7 +46,7 @@ export default class WeblateProjectTranslatedPercentage extends BaseJsonService } async fetch({ project, server = 'https://hosted.weblate.org' }) { - return this._requestJson({ + return super.fetch({ schema, url: `${server}/api/projects/${project}/statistics/`, errorMessages: { diff --git a/services/weblate/weblate-user-statistic.service.js b/services/weblate/weblate-user-statistic.service.js index 7a276a515b..fa697194e5 100644 --- a/services/weblate/weblate-user-statistic.service.js +++ b/services/weblate/weblate-user-statistic.service.js @@ -1,7 +1,7 @@ import Joi from 'joi' -import { BaseJsonService } from '../index.js' -import { nonNegativeInteger, optionalUrl } from '../validators.js' +import { nonNegativeInteger } from '../validators.js' import { metric } from '../text-formatters.js' +import WeblateBase from './weblate-base.js' const schema = Joi.object({ translated: nonNegativeInteger, @@ -11,10 +11,6 @@ const schema = Joi.object({ languages: nonNegativeInteger, }).required() -const queryParamSchema = Joi.object({ - server: optionalUrl, -}).required() - const statisticKeyNames = { translations: 'translated', suggestions: 'suggested', @@ -23,13 +19,14 @@ const statisticKeyNames = { languages: 'languages', } -export default class WeblateUserStatistic extends BaseJsonService { +export default class WeblateUserStatistic extends WeblateBase { static category = 'other' + static route = { base: 'weblate', pattern: ':statistic(translations|suggestions|languages|uploads|comments)/:user', - queryParamSchema, + queryParamSchema: this.queryParamSchema, } static examples = [ @@ -49,7 +46,7 @@ export default class WeblateUserStatistic extends BaseJsonService { } async fetch({ user, server = 'https://hosted.weblate.org' }) { - return this._requestJson({ + return super.fetch({ schema, url: `${server}/api/users/${user}/statistics/`, errorMessages: {