Skip to content

Commit

Permalink
feat: add option to sort migrations with naturalSort option
Browse files Browse the repository at this point in the history
* fix: add @adonisjs/ace as dev dependency

* test(migrator): add naturalSort feature

* feat(migrator): add naturalSort feature

* test(migrator): remove 'only' flag

* test(migrator): write back the db configuration before assertion

* test(commands): use @adonisjs/core standalone build instead of @adonisjs/ace

* ci: skip husky when installing npm deps

* fix: make FileNode.filename optional

* test(migrator): use path.sep to test path and remove batch assertion

* test(migrator): verify that natural sort work for nested file
  • Loading branch information
RomainLanz authored Dec 21, 2020
1 parent f189e57 commit cbf0f3c
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 17 deletions.
10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
FROM node:12.0.0-alpine as build-deps

RUN apk update && apk upgrade && \
apk add --update git && \
apk add --update openssh && \
apk add --update bash && \
apk add --update wget
apk add --update git && \
apk add --update openssh && \
apk add --update bash && \
apk add --update wget

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install
RUN HUSKY_SKIP_INSTALL=1 npm install

COPY . .
RUN npm run build
2 changes: 2 additions & 0 deletions adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ declare module '@ioc:Adonis/Lucid/Database' {
* implementation
*/
export type FileNode<T extends any> = {
filename?: string
absPath: string
name: string
getSource: () => T
Expand Down Expand Up @@ -274,6 +275,7 @@ declare module '@ioc:Adonis/Lucid/Database' {
paths?: string[]
tableName?: string
disableRollbacksInProduction?: boolean
naturalSort?: boolean
}

/**
Expand Down
109 changes: 104 additions & 5 deletions npm-audit.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.3.1/build/styles/atom-one-dark.min.css">

<title>NPM Audit Report</title>
<meta name="description" content="0 known vulnerabilities found.">
<meta name="description" content="2 known vulnerabilities found.">

<style>
pre {
Expand All @@ -39,23 +39,23 @@ <h1 class="mt-5 text-center">NPM Audit Report</h1>
<div class="card">
<div class="card-body">
<h5 class="card-title">
0
2
</h5>
<p class="card-text">Known vulnerabilities</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">
177
195
</h5>
<p class="card-text">Dependencies</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">
December 1st 2020, 1:23:10 pm
December 14th 2020, 7:15:17 pm
</h5>
<p class="card-text">Last updated</p>
</div>
Expand Down Expand Up @@ -95,7 +95,7 @@ <h5 class="card-title">
<div class="card">
<div class="card-body">
<h5 class="card-title">
0
2
</h5>
<p class="card-text">
<span class="badge badge-primary">low</span>
Expand Down Expand Up @@ -128,13 +128,112 @@ <h5 class="card-title">
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
<a href="https://npmjs.com/advisories/1589" data-toggle="modal" data-target="#advisory-modal-1589">Prototype Pollution</a>
</th>
<td>
<a href="https://npmjs.com/package/ini" target="_blank"
rel="noopener">ini</a>
</td>
<td data-order="4"><span
class="badge badge-primary">low</span></td>
<td>
CWE-471
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>


<div class="modal" tabindex="-1" role="dialog" id="advisory-modal-1589">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<span class="badge badge-primary">low</span>
Prototype Pollution
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<ul>
<li>Module:
<a href="https://npmjs.com/package/ini" target="_blank"
rel="noopener">ini</a>
</li>
<li>Published: December 9th 2020 </li>
<li>Reported by: Gur Shafriri</li>
<li>CWE-471</li>
</ul>
</div>
<div class="col-md-6">
<ul>
<li>Vulnerable: &lt;1.3.6</li>
<li>Patched: &gt;1.3.6</li>
<li>Exploitability: 3</li>
</ul>
</div>
</div>
<h3>Overview</h3>
<p class="card-text"><p><code>ini</code> before version 1.3.6 has a Prototype Pollution vulnerability.</p>
<h3 id="impact">Impact</h3>
<p>If an attacker submits a malicious INI file to an application that parses it with <code>ini.parse</code>, they will pollute the prototype on the application. This can be exploited further depending on the context.</p>
<h3 id="patches">Patches</h3>
<p>This has been patched in 1.3.6</p>
<h3 id="steps-to-reproduce">Steps to reproduce</h3>
<p>payload.ini</p>
<pre><code><span class="hljs-section">[__proto__]</span>
<span class="hljs-attr">polluted</span> = <span class="hljs-string">&quot;polluted&quot;</span></code></pre>
<p>poc.js:</p>
<pre><code><span class="hljs-keyword">var</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;fs&#x27;</span>)
<span class="hljs-keyword">var</span> ini = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;ini&#x27;</span>)

<span class="hljs-keyword">var</span> parsed = ini.parse(fs.readFileSync(<span class="hljs-string">&#x27;./payload.ini&#x27;</span>, <span class="hljs-string">&#x27;utf-8&#x27;</span>))
<span class="hljs-built_in">console</span>.log(parsed)
<span class="hljs-built_in">console</span>.log(parsed.__proto__)
<span class="hljs-built_in">console</span>.log(polluted)</code></pre>
<pre><code>&gt; <span class="hljs-selector-tag">node</span> <span class="hljs-selector-tag">poc</span><span class="hljs-selector-class">.js</span>
{}
{ <span class="hljs-attribute">polluted</span>: <span class="hljs-string">&#x27;polluted&#x27;</span> }
{ <span class="hljs-attribute">polluted</span>: <span class="hljs-string">&#x27;polluted&#x27;</span> }
<span class="hljs-selector-tag">polluted</span>
</code></pre>
</p>

<h3>Findings</h3>
<ul>
<li>knex&gt;liftoff&gt;findup-sync&gt;resolve-dir&gt;global-modules&gt;global-prefix&gt;ini </li>
<li>knex-dynamic-connection&gt;knex&gt;liftoff&gt;findup-sync&gt;resolve-dir&gt;global-modules&gt;global-prefix&gt;ini </li>
</ul>

<h3>Remediation</h3>
<p class="card-text"><p>Upgrade to version 1.3.6 or later.</p>
</p>

<h3>References</h3>
<p class="card-text"><ul>
<li><a href="https://github.com/npm/ini/commit/56d2805e07ccd94e2ba0984ac9240ff02d44b6f1">Fix Commit</a></li>
<li><a href="https://github.com/advisories/GHSA-qqgx-2p2h-9c37">GitHub Advisory</a></li>
</ul>
</p>

</div>
<div class="modal-footer">
<a class="btn btn-raised mr-2 btn-primary" href="https://npmjs.com/advisories/1589" target="_blank" rel="noopener">More about
this vulnerability</a>
<button type="button" class="btn btn-raised btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
Expand Down
9 changes: 8 additions & 1 deletion src/Migrator/MigrationSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ export class MigrationSource {
* paths are resolved from the project root
*/
private async getDirectoryFiles(directoryPath: string): Promise<FileNode<unknown>[]> {
const { files } = await sourceFiles(this.app.appRoot, directoryPath)
let { files } = await sourceFiles(this.app.appRoot, directoryPath)

if (this.config.migrations?.naturalSort) {
files = files.sort((a, b) =>
a.filename!.localeCompare(b.filename!, undefined, { numeric: true, sensitivity: 'base' })
)
}

return files
}

Expand Down
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ export function sourceFiles(
try {
resolve({
directory,
files: files.sort().map((file) => {
files: files.sort().map((file: string) => {
return {
filename: file,
absPath: join(path, file),
name: join(directory, file.replace(RegExp(`${extname(file)}$`), '')),
getSource() {
Expand Down
2 changes: 1 addition & 1 deletion test/commands/db-seed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import test from 'japa'
import 'reflect-metadata'
import { Kernel } from '@adonisjs/ace'
import { Kernel } from '@adonisjs/core/build/standalone'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

import DbSeed from '../../commands/DbSeed'
Expand Down
2 changes: 1 addition & 1 deletion test/commands/make-migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import test from 'japa'
import 'reflect-metadata'
import { join } from 'path'
import { Kernel } from '@adonisjs/ace'
import { Kernel } from '@adonisjs/core/build/standalone'
import { Filesystem } from '@poppinss/dev-utils'

import { Database } from '../../src/Database'
Expand Down
2 changes: 1 addition & 1 deletion test/commands/make-model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import test from 'japa'
import 'reflect-metadata'
import { join } from 'path'
import { Kernel } from '@adonisjs/ace'
import { Kernel } from '@adonisjs/core/build/standalone'
import { Filesystem } from '@poppinss/dev-utils'
import { toNewlineArray, fs, setupApplication } from '../../test-helpers'

Expand Down
2 changes: 1 addition & 1 deletion test/commands/migrate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import test from 'japa'
import 'reflect-metadata'
import { join } from 'path'
import { Kernel } from '@adonisjs/ace'
import { Kernel } from '@adonisjs/core/build/standalone'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

import Migrate from '../../commands/Migration/Run'
Expand Down
106 changes: 105 additions & 1 deletion test/migrations/migrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// <reference path="../../adonis-typings/index.ts" />

import test from 'japa'
import { join } from 'path'
import { join, sep } from 'path'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

import {
Expand Down Expand Up @@ -966,6 +966,110 @@ test.group('Migrator', (group) => {
assert.equal(migrator.status, 'error')
})

test('use a natural sort to order files when configured', async (assert) => {
const originalConfig = Object.assign({}, db.getRawConnection('primary')!.config)

db.getRawConnection('primary')!.config.migrations = {
naturalSort: true,
}

await fs.add(
'database/migrations/12_users.ts',
`
import { Schema } from '../../../../../src/Schema'
module.exports = class User extends Schema {
public async up () {
this.schema.createTable('schema_users', (table) => {
table.increments()
})
}
public async down () {
this.schema.dropTable('schema_users')
}
}
`
)

await fs.add(
'database/migrations/1_accounts.ts',
`
import { Schema } from '../../../../../src/Schema'
module.exports = class User extends Schema {
public async up () {
this.schema.createTable('schema_accounts', (table) => {
table.increments()
})
}
public async down () {
this.schema.dropTable('schema_accounts')
}
}
`
)

const migrator = getMigrator(db, app, { direction: 'up', connectionName: 'primary' })
await migrator.run()
const files = await migrator.getList()

db.getRawConnection('primary')!.config = originalConfig

assert.lengthOf(files, 2)
assert.equal(files[0].name, `database${sep}migrations${sep}1_accounts`)
assert.equal(files[1].name, `database${sep}migrations${sep}12_users`)
})

test('use a natural sort to order nested files when configured', async (assert) => {
const originalConfig = Object.assign({}, db.getRawConnection('primary')!.config)

db.getRawConnection('primary')!.config.migrations = {
naturalSort: true,
}

await fs.add(
'database/migrations/1/12_users.ts',
`
import { Schema } from '../../../../../src/Schema'
module.exports = class User extends Schema {
public async up () {
this.schema.createTable('schema_users', (table) => {
table.increments()
})
}
public async down () {
this.schema.dropTable('schema_users')
}
}
`
)

await fs.add(
'database/migrations/12/1_accounts.ts',
`
import { Schema } from '../../../../../src/Schema'
module.exports = class User extends Schema {
public async up () {
this.schema.createTable('schema_accounts', (table) => {
table.increments()
})
}
public async down () {
this.schema.dropTable('schema_accounts')
}
}
`
)

const migrator = getMigrator(db, app, { direction: 'up', connectionName: 'primary' })
await migrator.run()
const files = await migrator.getList()

db.getRawConnection('primary')!.config = originalConfig

assert.lengthOf(files, 2)
assert.equal(files[0].name, `database${sep}migrations${sep}1${sep}12_users`)
assert.equal(files[1].name, `database${sep}migrations${sep}12${sep}1_accounts`)
})

test('raise exception when rollbacks in production are disabled', async (assert) => {
app.nodeEnvironment = 'production'
const originalConfig = Object.assign({}, db.getRawConnection('primary')!.config)
Expand Down

0 comments on commit cbf0f3c

Please sign in to comment.