Skip to content

Commit

Permalink
Document private/secret.json; affects [bower jira] (#2599)
Browse files Browse the repository at this point in the history
* don't use a libraries.io token for bower integration

The libraries.io docs claim you need to be authenticated
to make any API request: https://libraries.io/api#authentication

In practice we can call https://libraries.io/api/bower/jquery
just fine with no token and based on chucking a load of
requests at it and examining the `x-ratelimit-remaining`
headers you actually seem to get a better limit with no
authentication.

All of our libraries.io badges in `services/librariesio`
seem to have been running fine with no token for some time.


* change jira auth settings to jira_user, jira_pass

All the other services use servicename_user, servicename_pass

This switches JIRA to use that convention by preference
but supports _username and _password for legacy users.


* add docs for server secrets


* add danger rule for server-secrets.md

this rule prompts users to update server-secrets.md
if 'serverSecrets' is in the diff
  • Loading branch information
chris48s authored Jan 2, 2019
1 parent 5dc25af commit d96d8ae
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 100 deletions.
16 changes: 16 additions & 0 deletions dangerfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const logos = fileMatch('logo/*.svg')
const helperTests = fileMatch('lib/**/*.spec.js')
const packageJson = fileMatch('package.json')
const packageLock = fileMatch('package-lock.json')
const secretsDocs = fileMatch('doc/server-secrets.md')
const capitals = fileMatch('**/*[A-Z]*.js')
// _document.js is used by convention by Next.
const underscores = fileMatch('**/*_*.js', '!pages/_document.js')
Expand Down Expand Up @@ -129,6 +130,21 @@ allFiles.forEach(file => {
})
})

allFiles.forEach(file => {
// eslint-disable-next-line promise/prefer-await-to-then
danger.git.diffForFile(file).then(diff => {
if (/serverSecrets/.test(diff.diff) && !secretsDocs.modified) {
warn(
[
`:books: Remember to ensure any changes to \`serverSecrets\` `,
`in \`${file}\` are reflected in the [server secrets documentation]`,
'(https://github.com/badges/shields/blob/master/doc/server-secrets.md)',
].join('')
)
}
})
})

function onlyUnique(value, index, self) {
return self.indexOf(value) === index
}
Expand Down
6 changes: 6 additions & 0 deletions doc/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,12 @@ should be included. They serve several purposes:
There is a dedicated [tutorial for tests in the service-tests folder](service-tests.md).
Please follow it to include tests on your pull-request.

### (4.6) Update the Docs

If your submission require an API token or authentication credentials, please
update [server-secrets.md](./server-secrets.md). You should explain what the
token or credentials are for and how to obtain them.

## (5) Create a Pull Request

Once you have implemented a new badge:
Expand Down
29 changes: 1 addition & 28 deletions doc/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,34 +125,7 @@ Server secrets

You can add your own server secrets in `private/secret.json`.

Because of Github rate limits, you will need to provide a token, or else badges
will stop working once you hit 60 requests per hour, the
[unauthenticated rate limit][github rate limit].

You can [create a personal access token][personal access tokens] through the
Github website. When you create the token, you can choose to give read access
to your repositories. If you do that, your self-hosted Shields installation
will have access to your private repositories.

```
{
"gh_token": "..."
}
```

When a `gh_token` is specified, it is used in place of the Shields token
rotation logic.

You can also give your self-hosted Shields installation access to private npm
packages by [generating an npm token][npm token] and using that for the `npm_token` value.

To integrate with Wheelmap.org, you can [sign into your account][wheelmap token] and use
the _Authentication Token_ displayed on your profile page for the `wheelmap_token` value.

[github rate limit]: https://developer.github.com/v3/#rate-limiting
[personal access tokens]: https://github.com/settings/tokens
[npm token]: https://docs.npmjs.com/getting-started/working_with_tokens
[wheelmap token]: http://classic.wheelmap.org/en/users/sign_in
These are documented in [server-secrets.md](./server-secrets.md)

Separate frontend hosting
-------------------------
Expand Down
120 changes: 120 additions & 0 deletions doc/server-secrets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Server Secrets

It is possible to provide a token or credentials for a number of external
services. These may be used to lift a rate limit or provide access to
private resources from a self-hosted instance.

Secrets can be set in `private/secret.json`. For example:

```
{
"gh_token": "..."
}
```

## Bintray

* `bintray_user`
* `bintray_apikey`

The bintray API [requires authentication](https://bintray.com/docs/api/#_authentication)
Create an account and obtain a token from the user profile page.


## GitHub

* `gh_token`

Because of Github rate limits, you will need to provide a token, or else badges
will stop working once you hit 60 requests per hour, the
[unauthenticated rate limit][github rate limit].

You can [create a personal access token][personal access tokens] through the
Github website. When you create the token, you can choose to give read access
to your repositories. If you do that, your self-hosted Shields installation
will have access to your private repositories.

When a `gh_token` is specified, it is used in place of the Shields token
rotation logic.

[github rate limit]: https://developer.github.com/v3/#rate-limiting
[personal access tokens]: https://github.com/settings/tokens

* `gh_client_id`
* `gh_client_secret`

These settings are used by shields.io for GitHub OAuth app authorization
but will not be necessary for most self-hosted installations. See
[production-hosting.md](./production-hosting.md).

## Jenkins CI

* `jenkins_user`
* `jenkins_pass`

Provide a username and password to give your self-hosted Shields installation
access to a private Jenkins CI instance.

## JIRA

* `jira_user`
* `jira_pass`

Provide a username and password to give your self-hosted Shields installation
access to a private JIRA instance.

For legacy reasons `jira_username` and `jira_password` are also supported
but may be removed in future.

## Nexus

* `nexus_user`
* `nexus_pass`

Provide a username and password to give your self-hosted Shields installation
access to your private nexus repositories.

## NPM

* `npm_token`

[Generate an npm token][npm token] to give your self-hosted Shields
installation access to private npm packages

[npm token]: https://docs.npmjs.com/getting-started/working_with_tokens

## Sentry

* `sentry_dsn`

A [Sentry DSN](https://docs.sentry.io/error-reporting/quickstart/?platform=javascript#configure-the-dsn)
may be used to send error reports from your installation to
[Sentry.io](http://sentry.io/). For more info, see the
[self hosting docs](https://github.com/badges/shields/blob/master/doc/self-hosting.md#sentry).

## SymfonyInsight (formerly Sensiolabs)

* `sl_insight_userUuid`
* `sl_insight_apiToken`

The SymfonyInsight API requires authentication. To obtain a token,
Create an account, sign in and obtain a uuid and token from your
[account page](https://insight.sensiolabs.com/account).

## SonarQube

* `sonarqube_token`

[Generate a token](https://docs.sonarqube.org/latest/user-guide/user-token/)
to give your self-hosted Shields installation access to a
private SonarQube instance or private project on a public instance.

## Wheelmap

* `wheelmap_token`

The wheelmap API requires authentication. To obtain a token,
Create an account, [sign in][wheelmap token] and use the _Authentication Token_
displayed on your profile page.

[wheelmap token]: http://classic.wheelmap.org/en/users/sign_in
7 changes: 1 addition & 6 deletions services/bower/bower-license.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const LegacyService = require('../legacy-service')
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
const serverSecrets = require('../../lib/server-secrets')

// This legacy service should be rewritten to use e.g. BaseJsonService.
//
Expand Down Expand Up @@ -45,11 +44,7 @@ module.exports = class BowerLicense extends LegacyService {
json: true,
uri: `https://libraries.io/api/bower/${repo}`,
}
if (serverSecrets && serverSecrets.libraries_io_api_key) {
options.qs = {
api_key: serverSecrets.libraries_io_api_key,
}
}

request(options, (err, res, data) => {
if (err != null) {
badgeData.text[1] = 'inaccessible'
Expand Down
7 changes: 1 addition & 6 deletions services/bower/bower-version.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const LegacyService = require('../legacy-service')
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
const { addv: versionText } = require('../../lib/text-formatters')
const { version: versionColor } = require('../../lib/color-formatters')
const serverSecrets = require('../../lib/server-secrets')

// This legacy service should be rewritten to use e.g. BaseJsonService.
//
Expand Down Expand Up @@ -51,11 +50,7 @@ module.exports = class BowerVersion extends LegacyService {
json: true,
uri: `https://libraries.io/api/bower/${repo}`,
}
if (serverSecrets && serverSecrets.libraries_io_api_key) {
options.qs = {
api_key: serverSecrets.libraries_io_api_key,
}
}

request(options, (err, res, data) => {
if (err != null) {
badgeData.text[1] = 'inaccessible'
Expand Down
15 changes: 12 additions & 3 deletions services/jira/jira-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,19 @@ module.exports = class JiraBase extends BaseJsonService {
async fetch({ url, qs, schema, errorMessages }) {
const options = { qs }

if (serverSecrets && serverSecrets.jira_username) {
if (
serverSecrets &&
(serverSecrets.jira_user || serverSecrets.jira_username)
) {
/*
for legacy reasons we still allow
jira_username, jira_password
but prefer
jira_user, jira_pass
*/
options.auth = {
user: serverSecrets.jira_username,
pass: serverSecrets.jira_password,
user: serverSecrets.jira_user || serverSecrets.jira_username,
pass: serverSecrets.jira_pass || serverSecrets.jira_password,
}
}

Expand Down
53 changes: 30 additions & 23 deletions services/jira/jira-issue.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

const t = (module.exports = require('../create-service-tester')())
const { colorScheme } = require('../test-helpers')
const { mockJiraCreds, restore, user, pass } = require('./jira-test-helpers')
const {
mockJiraCreds,
mockLegacyJiraCreds,
restore,
user,
pass,
} = require('./jira-test-helpers')

t.create('live: unknown issue')
.get('/https/issues.apache.org/jira/notArealIssue-000.json')
Expand Down Expand Up @@ -162,26 +168,27 @@ t.create('blue-gray status color')
value: 'cloudy',
colorB: colorScheme.blue,
})

t.create('with auth')
.before(mockJiraCreds)
.get('/https/myprivatejira.com/secure-234.json')
.intercept(nock =>
nock('https://myprivatejira.com/rest/api/2/issue')
.get(`/${encodeURIComponent('secure-234')}`)
// This ensures that the expected credentials from serverSecrets are actually being sent with the HTTP request.
// Without this the request wouldn't match and the test would fail.
.basicAuth({
user,
pass,
})
.reply(200, {
fields: {
status: {
name: 'in progress',
;[mockJiraCreds, mockLegacyJiraCreds].map(mockCreds => {
t.create(`with auth (${mockCreds.name})`)
.before(mockCreds)
.get('/https/myprivatejira.com/secure-234.json')
.intercept(nock =>
nock('https://myprivatejira.com/rest/api/2/issue')
.get(`/${encodeURIComponent('secure-234')}`)
// This ensures that the expected credentials from serverSecrets are actually being sent with the HTTP request.
// Without this the request wouldn't match and the test would fail.
.basicAuth({
user,
pass,
})
.reply(200, {
fields: {
status: {
name: 'in progress',
},
},
},
})
)
.finally(restore)
.expectJSON({ name: 'secure-234', value: 'in progress' })
})
)
.finally(restore)
.expectJSON({ name: 'secure-234', value: 'in progress' })
})
Loading

0 comments on commit d96d8ae

Please sign in to comment.