44 Commits
v1.x ... master

Author SHA1 Message Date
Jean-Baptiste Nahan
de248f9e99 Merge pull request #11 from Mactronique/sf4_php74
bump version Symfony and PHP 7.4
2020-06-10 17:14:10 +02:00
Jean-Baptiste Nahan
8052a36be3 Merge pull request #10 from Mactronique/add_pgsql
add Postgres SQL script
2020-06-10 17:12:34 +02:00
macintoshplus
04d2367a5e add Postgres SQL script 2019-10-29 22:19:23 +01:00
macintoshplus
89bed3d960 bump version Symfony and PHP 7.4 2019-10-11 22:51:04 +02:00
macintoshplus
812b5387f6 remove todo 2019-08-01 23:54:25 +02:00
macintoshplus
f4a8152053 update change log 2019-08-01 23:34:46 +02:00
macintoshplus
6ec2fd5806 Closes #8 2019-08-01 23:31:54 +02:00
macintoshplus
b556ad9568 fix internal and external security checker 2019-08-01 22:49:45 +02:00
Jean-Baptiste Nahan
7d2baabd1a Merge pull request #9 from Mactronique/add_platform_reqs
Add platform reqs from compose.lock
2019-08-01 21:42:39 +02:00
macintoshplus
dcbced5fe4 use only platform in composer.lock 2019-08-01 21:38:38 +02:00
macintoshplus
96662abf51 update deps 2019-07-22 22:18:41 +02:00
macintoshplus
ae89109ab6 start reading platform informations. see #5 #7 2019-06-06 21:20:55 +02:00
macintoshplus
307cfb2f04 Add SQL Example and SQL Script for create database 2019-05-29 22:29:22 +02:00
macintoshplus
612fe7852a use array for json decode 2019-05-28 21:17:17 +02:00
macintoshplus
e7bdbd0790 add alias 2019-05-28 20:49:10 +02:00
macintoshplus
1d4fd0656b fix typo and add docs 2019-05-28 18:45:00 +02:00
macintoshplus
ae1e87c6cc add dbal project provider 2019-05-28 16:49:19 +02:00
macintoshplus
58789c1e0a add config example file 2019-05-28 16:48:48 +02:00
macintoshplus
5d0c92665c fix typo and add docs 2019-05-28 14:46:21 +02:00
macintoshplus
79f9f15a06 fix typo 2019-05-28 14:45:21 +02:00
macintoshplus
254127e34c add MSQSL script for init database 2019-05-28 12:15:34 +02:00
macintoshplus
7d43b3c5ea update copyright 2019-05-28 09:16:24 +02:00
macintoshplus
26d41642c7 add php7.3 2019-05-28 09:16:13 +02:00
macintoshplus
a61a8d8f36 update use composer 2019-05-28 09:08:56 +02:00
macintoshplus
eed13c5f07 update copyright 2019-05-28 09:00:14 +02:00
macintoshplus
3385fbc916 Merge branch 'master' into feature_security_redmine_conf 2019-05-28 08:58:01 +02:00
Macintoshplus
bcf7916ba0 add repos info and limit to active project 2018-08-30 22:50:30 +02:00
Macintoshplus
eb9d8fc485 fix config loading from db 2018-08-30 22:43:56 +02:00
Macintoshplus
160cdbce8f fix config loading from db 2018-08-30 22:41:59 +02:00
Macintoshplus
0116f8d256 fixup! fix type for data on insert, update dependencies datas 2018-08-30 22:22:46 +02:00
Macintoshplus
ff01918aee fix type for data on insert, update dependencies datas 2018-08-30 22:15:03 +02:00
Macintoshplus
ac1f1f50a3 add internal and external security checker 2018-05-31 23:52:45 +02:00
Macintoshplus
1b08598a46 include security checker 2018-05-31 23:46:06 +02:00
Macintoshplus
88e575c6f8 #1 2018-05-25 15:19:48 +02:00
Macintoshplus
50382f5b19 config 2018-05-25 15:16:51 +02:00
Macintoshplus
e35bb667ce add upgrade guide 2018-05-25 15:16:35 +02:00
Macintoshplus
9a3a3b3cc1 Add security check and secure private library #1 #3 2018-05-25 15:16:01 +02:00
Macintoshplus
cd881c58f6 move source file to src 2018-05-25 09:07:02 +02:00
Macintoshplus
fb72e329a2 fix test 2018-04-25 12:33:35 +02:00
Macintoshplus
175aa45824 add security persistance #1 2018-04-25 11:51:38 +02:00
Macintoshplus
93bfa1e3f9 update readme 2018-04-25 11:14:54 +02:00
Macintoshplus
fa28a40610 update readme 2018-04-25 11:11:40 +02:00
Macintoshplus
e70da32c3f update readme 2018-04-25 11:10:44 +02:00
Macintoshplus
ea189abd17 add security check and refactor #1 #2 2018-04-25 10:56:41 +02:00
35 changed files with 1839 additions and 690 deletions

5
.gitignore vendored
View File

@@ -1,3 +1,6 @@
/vendor/
cua.yml
/build
/build
/*.yml
/*.yaml
!/.travis.yml

View File

@@ -5,6 +5,7 @@ php:
- 7.0
- 7.1
- 7.2
- 7.3
- nightly
cache:
@@ -21,6 +22,7 @@ env:
matrix:
allow_failures:
- php: nightly
- php: 7.4snapshot
- php: 5.6
exclude:
- php: 7.0
@@ -29,9 +31,19 @@ matrix:
env: COMPOSER_PREFER="--prefer-lowest"
- php: 7.2
env: COMPOSER_PREFER="--prefer-lowest"
- php: 7.3
env: COMPOSER_PREFER="--prefer-lowest"
- php: 7.1
env: COMPOSER_PREFER="--prefer-stable"
- php: 7.2
env: COMPOSER_PREFER="--prefer-stable"
- php: 7.3
env: COMPOSER_PREFER="--prefer-stable"
- php: 7.4snapshot
env: COMPOSER_PREFER="--prefer-lowest"
- php: nightly
env: COMPOSER_PREFER="--prefer-lowest"
script:
- composer update -o $COMPOSER_PREFER
- vendor/bin/atoum
- vendor/bin/atoum

18
ChangeLog.md Normal file
View File

@@ -0,0 +1,18 @@
Change log
==========
# 2.0.0
* Add security check
* Enable to check one specific project found in the project configuration
* Allow to check one custom project not found in project configuration
* Move source file in src directory
* Add DBAL configuration provider
* Add Redmine configuration provider (experimental)
* Add DBAL Storage for dependencies and security issues
* Add reporting the PHP version and PHP extension dependencies from composer.lock
# 1.0.0
* First version
* Check dependencies update, remove and deprecated

BIN
Cua_Exemple_SQLite.db Normal file

Binary file not shown.

View File

@@ -1,13 +1,37 @@
.PHONY: check
##
## This file is part of Composer Update Analyser package.
##
## @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
## @copyright 2016-2019 - Jean-Baptiste Nahan
## @license MIT
##
check: vendor
docker-compose run --rm tool bash -ci 'phpdismod xdebug && composer self-update && ./cua check $(c)'
.PHONY: check security console run-tests
run-tests:
check: vendor cua.yml
docker-compose run --rm tool bash -ci 'phpdismod xdebug && php composer.phar self-update && ./cua check $(c)'
security: vendor cua.yml
docker-compose run --rm tool bash -ci 'phpdismod xdebug && ./cua security $(c)'
console:
docker-compose run --rm tool bash
run-tests: vendor
docker-compose run --rm tool bash -ci 'vendor/bin/atoum'
vendor: composer.lock
docker-compose run --rm tool bash -ci 'phpdismod xdebug && composer self-update && composer install -o --prefer-dist'
docker-compose run --rm tool bash -ci 'phpdismod xdebug && php composer.phar self-update && php composer.phar install -o --prefer-dist'
composer.lock: composer.json
docker-compose run --rm tool bash -ci 'phpdismod xdebug && composer self-update && composer update -o --prefer-dist'
composer.lock: composer.json composer.phar
docker-compose run --rm tool bash -ci 'phpdismod xdebug && php composer.phar self-update && php composer.phar update -o --prefer-dist'
composer.phar:
$(eval EXPECTED_SIGNATURE = "$(shell wget -q -O - https://composer.github.io/installer.sig)")
$(eval ACTUAL_SIGNATURE = "$(shell php -r "copy('https://getcomposer.org/installer', 'composer-setup.php'); echo hash_file('SHA384', 'composer-setup.php');")")
@if [ "$(EXPECTED_SIGNATURE)" != "$(ACTUAL_SIGNATURE)" ]; then echo "Invalid signature"; exit 1; fi
php composer-setup.php
rm composer-setup.php
cua.yml: cua.yml.dist
cp cua.yml.dist cua.yml

211
README.md
View File

@@ -1,16 +1,27 @@
# cua
Composer Update Analyser
Composer Update Analyzer
[![Build Status](https://travis-ci.org/InExtenso/cua.svg?branch=master)](https://travis-ci.org/InExtenso/cua) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Build Status](https://travis-ci.org/Mactronique/cua.svg?branch=master)](https://travis-ci.org/Mactronique/cua) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
This utility can read the `composer.lock` for get all library used by your project and run `composer update --dry-run` for get all necessary install, update or remove actions.
This utility can
* Read the `composer.lock` to get all library used by your project and run `composer update --dry-run` to get all necessary install, update or remove actions.
* Run security-checker to get all library actually installed with know security issues. __This feature send the `composer.lock` file at the remote service__.
The output can is a `yaml` file or DBAL table.
The output can be a `yaml` file or DBAL table.
## Requirements
* composer
* php 5.6+
* [composer](https://getcomposer.org/)
* [PHP 5.6+](http://php.net)
* [security-checker](https://security.symfony.com/)
## Recommended software
> For Linux only.
* [Docker](https://docs.docker.com/install/)
* [Docker compose](https://docs.docker.com/compose/install/)
* [Make](https://en.wikipedia.org/wiki/Makefile)
## Install
@@ -19,57 +30,191 @@ The output can is a `yaml` file or DBAL table.
## Configure
Create an file `cua.yml` at root cua folder.
Add a file `cua.yml` (or copy the `cual.yml.dist` file) at the root cua folder.
Put content:
Put content :
```
projects:
project_name: /path/to/project
composer_path: /usr/local/bin/composer # the path to composer
composer_path: /usr/local/bin/composer # the path to the composer executable
security_checker_path: /usr/bin/security-checker # The path to security-checker executable
# (set 'internal' if you want use the Internal Security Checker)
persistance:
format: DbalPersistance # Persistence
parameters:
dbname: 'deps' # DBAL Database name
user: 'root' # DBAL database username
password: 'root' # DBALbatabase user password
host: 'localhost' # DBAL server name or ip
driver: 'pdo_mysql' # DBAL Driver name
table_name: 'dependencies' # the table name for DBAL persistance
path: ./all.yml # for YamlFile only
dbname: 'deps' # DBAL Database name
user: 'root' # DBAL database username
password: 'root' # DBALbatabase user password
host: 'localhost' # DBAL server name or IP
driver: 'pdo_mysql' # DBAL Driver name
table_name: 'dependencies' # the table name for DBAL persistence
table_name_security: 'security' # the table name for DBAL persistence
path: ./all.yml # for YamlFile only
path_security: ./all_security.yml # for YamlFile only
project_provider:
type: file
parameters:
path: projects.yml # Location of the file project list of root cua install
```
### Usign with Redmine Cua Plugin
Add a file `project.yml` (or copy the `project.yml.dist` file) at the root cua folder.
Put content:
```
projects:
'project_name':
path: /path/to/project
check_dependencies: true # Optional, by default : true
lock_path: ./composer.lock # Optional, by default : ./composer.lock. Set the location of composer.lock file from the project path.
check_security: false # Optional, by default : false. Enable this project for command security
php_path: php7.2 # Optional, by default : php. The php executable name (or path) for run security-checker and composer
```
### Store in file
In the `cua.yml` file, set the property `persistance.format` to the `YamlFile`.
Define two properties `path` and `path_security` with the path when you want store the result.
> Note : The content is overwritten for each execution.
Configuration example:
```
[...]
persistance:
format: YamlFile # Persistence
parameters:
path: ./all.yml # The content of library update
path_security: ./all_security.yml # The content of security issues
[...]
```
### Store in Relational Databases (with DBAL)
In the `cua.yml` file, set the property `persistance.format` to the `DbalPersistance`.
All script for creating tables in your database is in this folder `src/Sql/`. If your RDBMS is not present, you cal use on script and update it for your usage.
The parameters are the same for [Doctrine DBAL](https://www.doctrine-project.org/projects/doctrine-dbal/en/2.9/reference/configuration.html) with two additional fields `table_name` and `table_name_security`.
> Note : The PHP used for running the cua application do need the driver for the database.
Configuration example:
```
[...]
persistance:
format: DbalPersistance # Persistence
parameters:
dbname: 'deps' # DBAL Database name
user: 'root' # DBAL database username
password: 'root' # DBALbatabase user password
host: 'localhost' # DBAL server name or IP
driver: 'pdo_mysql' # DBAL Driver name
table_name: 'dependencies' # the table name for the dependencies DBAL persistence
table_name_security: 'security' # the table name for the security DBAL persistence
[...]
```
### Use with Redmine Cua Plugin (deprecated)
Set the format to `RedmineCuaPersistance` and `table_name` to `cua_dependencies`
In this case, the `project_name` set into the config file do same the project identifier from redmine.
In this case, the `project_name` set into the config file do the same project identifier from redmine.
### DBAL Table creation
### File Project Provider
If you use MySQL for persist, you can use the file `Sql/Create_Table.sql` for create table in your database.
This file contains all configuration for each project.
Set the property `project_provider.type` to `file`.
Set the property `project_provider.parameters.path` to the name of file contains all projects configuration.
> Note : The file path is relative to the cua installation folder.
Exemple of configuration:
```
[...]
project_provider:
type: file
parameters:
path: projects.yml # Location of the file project list of root cua install
```
## Usage
### Dbal Project Provider
open console, go to cua root folder and type :
You can store the configuration into a database. The SQL script present into the folder `src/Sql` contains the table `projects` description.
The table fields descriptions:
* The field `code` of type string and 10 characters maximum of length is needed and contain a unique identity for the project
* The field `name` of type string and 100 characters maximum of length is needed and contain user-friendly project name
* The field `path` of type string and 255 characters maximum of length is needed and contain the path to the root project folder
* The field `lock_path` of type string and 255 characters maximum of length is needed and contain the relative path (from the root project path) to the `composer.lock` file.
* The field `php_path` of type string and 255 characters maximum of length is needed and contain the PHP executable name or full path.
* The field `private_dependencies` of type text is optional. If defined this field do contain a JSON representation of the array. This array contains all private dependency name. Used only for the security check.
* The field `private_dependencies_strategy` of type string and 10 characters maximum of length is optional, the default value is `remove`. The value can be `remove` or `hash`. This defines the behavior for the private dependency before send the `composer.lock` file for security checks.
* The field `check_dependencies` of type tinyint(1) is optional, if defined to 1, enable the dependency check for this project.
* The field `check_security` of type tinyint(1) is optional, if defined to 1, enable the security check for this project.
* The field `updated_at` of type datetime is needed and contains the last update date and time.
Example of configuration:
```
[...]
project_provider:
type: file
parameters:
working_dir: /sources # The root folder used in prefix for project path
db:
dbname: 'deps' # DBAL Database name
user: 'root' # DBAL database username
password: 'root' # DBALbatabase user password
host: 'localhost' # DBAL server name or IP
driver: 'pdo_mysql' # DBAL Driver name
table_name: projects # The table name for read the project configuration
```
## Check project configuration
Open console, go to the cua root folder and type :
```
php ./cua project:list
```
This command print a table with all project configured and details.
## Usage for check dependencies
Open console, go to the cua root folder and type :
```
php ./cua check
```
This command launch the process for all setting project and store in persistance.
This command launch the process for all setting project and store in persistence.
## Usage for check security
Open console, go to the cua root folder and type :
```
php ./cua security
```
This command launch the process for all setting project and store in persistence.
# Contribute
If you whant contribute, please fork my repo, add feature or fix bugs and create new pull request.
# Todo
[ ] Add script for create table on other Database (postgre, mssql, etc.).
[ ] Get PHP and extentions requirements by project.
If you want to contribute, please fork my repo, add features or fix bugs and create a new pull request.

View File

@@ -1,32 +0,0 @@
##
## This file is part of Composer Update Analyser package.
##
## @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
## @copyright 2016-2019 - Jean-Baptiste Nahan
## @license MIT
##
## FOR MySQL
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS `dependencies` (
`id` int(11) NOT NULL,
`project` varchar(50) NOT NULL,
`library` varchar(250) NOT NULL,
`version` varchar(250) NOT NULL,
`state` varchar(20) NOT NULL,
`to_library` varchar(250) DEFAULT NULL,
`to_version` varchar(250) DEFAULT NULL,
`deprecated` tinyint(1) DEFAULT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `dependencies`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `idx_proj_lib` (`project`,`library`(191)) USING BTREE,
ADD KEY `idx_plv` (`project`,`library`(191),`version`(191));
ALTER TABLE `dependencies`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

70
UPGRADE_TO_2.0.md Normal file
View File

@@ -0,0 +1,70 @@
UPGRADE FROM 1.0 to 2.0
=======================
The version 2.0 rewrite the configuration and split the file in two files.
# Rewrite the configuration
Read the `projects` part from the 1.0 cua config file do move into an new file.
This is an example to split the old v1.0 config file:
## v1.0 config file `cua.yml`
```
projects:
project_name: /path/to/project
composer_path: /usr/local/bin/composer # the path to composer
persistance:
format: DbalPersistance # Persistence
parameters:
dbname: 'deps' # DBAL Database name
user: 'root' # DBAL database username
password: 'root' # DBALbatabase user password
host: 'localhost' # DBAL server name or ip
driver: 'pdo_mysql' # DBAL Driver name
table_name: 'dependencies' # the table name for DBAL persistance
path: ./all.yml # for YamlFile only
```
## Generate the v2.0 config file
v2.0 config file `cua.yml` is same as v1.0 without the `projets` configuration key:
```
composer_path: /usr/local/bin/composer # the path to composer
persistance:
format: DbalPersistance # Persistence
parameters:
dbname: 'deps' # DBAL Database name
user: 'root' # DBAL database username
password: 'root' # DBALbatabase user password
host: 'localhost' # DBAL server name or ip
driver: 'pdo_mysql' # DBAL Driver name
table_name: 'dependencies' # the table name for DBAL persistance
path: ./all.yml # for YamlFile only
```
Add the project source for use the projet YAML configuration file:
```
project_provider:
type: file
parameters:
path: projects.yml # Location of the file project list from root cua install
```
## Generate the 2.0 project configuration file
v2.0 project file configuration with the v1.0 `projets` configuration key:
```
projects:
project_name: /path/to/project
```
Il the new file, set the value of the `project name key` to the `path` key like this:
```
projects:
project_name:
path: /path/to/project
```

View File

@@ -1,41 +1,55 @@
{
"name": "mactronique/cua",
"type": "project",
"description": "This application can manage many project composer update",
"keywords": ["composer", "update", "dependecies", "management"],
"homepage": "https://github.com/Mactronique/cua",
"license": "MIT",
"authors": [
{
"name": "Jean-Baptiste Nahan",
"email": "814683+macintoshplus@users.noreply.github.com"
}
],
"autoload": {
"psr-4": {
"Mactronique\\CUA\\": ""
}
},
"autoload-dev": {
"psr-4": {
"Mactronique\\CUA\\Tests\\Units\\": "tests/Units"
}
},
"bin": ["cua"],
"require": {
"php": ">=5.6",
"symfony/console": "^3.0",
"symfony/yaml": "^3.0",
"symfony/config": "^3.0",
"psr/log": "^1.0",
"symfony/process": "^3.0",
"symfony/var-dumper": "^3.0",
"symfony/finder": "^3.0",
"doctrine/dbal": "^2.5"
},
"require-dev": {
"atoum/atoum": "^3.0",
"atoum/autoloop-extension": "*",
"atoum/reports-extension": "*"
"name": "mactronique/cua",
"type": "project",
"description": "This application can manage many project composer update",
"keywords": [
"composer",
"update",
"dependecies",
"management"
],
"homepage": "https://github.com/Mactronique/cua",
"license": "MIT",
"authors": [
{
"name": "Jean-Baptiste Nahan",
"email": "814683+macintoshplus@users.noreply.github.com"
}
],
"autoload": {
"psr-4": {
"Mactronique\\CUA\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Mactronique\\CUA\\Tests\\Units\\": "tests/Units"
}
},
"bin": [
"cua"
],
"require": {
"php": ">=5.6 || ^7.0",
"symfony/console": "^3.0 || ^4.0",
"symfony/yaml": "^3.0 || ^4.0",
"symfony/config": "^3.0 || ^4.0",
"psr/log": "^1.0",
"symfony/process": "^3.0 || ^4.0",
"symfony/var-dumper": "^3.0 || ^4.0",
"symfony/finder": "^3.0 || ^4.0",
"doctrine/dbal": "^2.5",
"sensiolabs/security-checker": "^5.0 || ^6.0",
"ext-json": "*"
},
"require-dev": {
"atoum/atoum": "^3.0 || dev-master",
"atoum/reports-extension": "*"
},
"extra": {
"branch-alias": {
"dev-master": "v2.x-dev",
"dev-v1.x": "v1.x-dev"
}
}
}

864
composer.lock generated

File diff suppressed because it is too large Load Diff

13
cua.yml.dist Normal file
View File

@@ -0,0 +1,13 @@
composer_path: composer # the path to the composer executable
security_checker_path: security-checker # The path to security-checker executable or 'internal' for use the integrated checker
persistance:
format: YamlFile
parameters:
path: ./all.yml # for YamlFile only
path_security: ./all_security.yml # for YamlFile only
project_provider:
type: file
parameters:
path: projects.yml # Location of the file project list of root cua install

View File

@@ -1,10 +1,10 @@
version: '2'
services:
tool:
image: macintoshplus/php:php71
image: macintoshplus/php:php72
volumes:
- ./:/sources
- /var/www:/var/www
- /usr/local/bin/composer:/usr/local/bin/composer
- /home/jbnahan/.config/composer/vendor/bin/composer:/usr/local/bin/composer
dns:
- 172.16.1.5
- 8.8.8.8

9
projects.yml.dist Normal file
View File

@@ -0,0 +1,9 @@
projects:
'cua':
path: ./
check_dependencies: true # Optional, by default : true
lock_path: ./composer.lock # Optional, by default : ./composer.lock. Set the location of composer.lock file from the project path.
check_security: false # Optional, by default : false. Enable this project for command security
php_path: php # Optional, by default : php. The php executable name (or path) for run security-checker and composer
private_dependencies: ~
private_dependencies_strategy: remove

View File

@@ -10,23 +10,25 @@
namespace Mactronique\CUA\Command;
use Mactronique\CUA\Service\CheckUpdateService;
use Mactronique\CUA\Service\InstalledLibraryService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CheckCommand extends Command
class CheckDependenciesCommand extends Command
{
protected function configure()
{
$this
->setName('check')
->setDescription('Execute les tests')
->setDescription('Run dependencies check')
->addArgument(
'name',
InputArgument::OPTIONAL,
'Nom du projet à vérifier'
'The project name to check'
)
->addOption(
'composer',
@@ -60,16 +62,34 @@ class CheckCommand extends Command
if (!file_exists($composerPath)) {
throw new \Exception('Invalid composer path '.$composerPath, 1);
}
$service = new \Mactronique\CUA\Service\CheckUpdateService($composerPath);
$service = new CheckUpdateService($composerPath);
$projects = $this->getApplication()->getProjects();
//Chargement du projet via la ligne de commande
$customName = $input->getArgument('name');
// Check one project found in configuration
if (null !== $customName && !$input->getOption('project')) {
if (!isset($projects[$customName])) {
throw new \Exception(sprintf('The name of project %s is not configured', $customName), 1);
}
$output->writeln(sprintf('Check for one specific project <info>"%s"</info>', $customName));
$projects = [$customName => $projects[$customName]];
}
//Load the project config from the commmand line
if ($input->getOption('project')) {
if (null === $input->getArgument('name')) {
if (null === $customName) {
throw new \Exception('Please set the name of project', 1);
}
$projects = [$input->getArgument('name') => $input->getOption('project')];
$customPath = $input->getOption('project');
$output->writeln(
sprintf(
'Check for one custom project <info>"%s"</info> at <info>"%s"</info>',
$customName,
$customPath
)
);
$projects = [$customName => ['path' => $customPath, 'check_dependencies' => true, 'php_path'=>'php']];
}
//Pas de fichier de config donc fichier de sortie obligatoire
@@ -78,11 +98,18 @@ class CheckCommand extends Command
}
$outputFile = $input->getOption('output');
$installedService = new \Mactronique\CUA\Service\InstalledLibraryService();
$installedService = new InstalledLibraryService();
foreach ($projects as $projectName => $projectPath) {
foreach ($projects as $projectName => $projectConf) {
$projectPath = $projectConf['path'];
$output->writeln(sprintf('Check <info>%s</info> at <comment>%s</comment>', $projectName, $projectPath));
$resultProject = $service->checkcomposerUpdate($projectPath);
if (!$projectConf['check_dependencies']) {
$output->writeln('<info>Skip</info>');
continue;
}
$resultProject = $service->checkcomposerUpdate($projectPath, $projectConf['php_path']);
if ($resultProject['error'] != '') {
$output->writeln(sprintf('<error> %s </error>', $resultProject['error']));

View File

@@ -0,0 +1,149 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\Command;
use Mactronique\CUA\Service\SecurityCheckService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CheckSecurityCommand extends Command
{
protected function configure()
{
$this
->setName('security')
->setDescription('Run security check')
->addArgument(
'name',
InputArgument::OPTIONAL,
'Nom du projet à vérifier'
)
->addOption(
'checker',
null,
InputOption::VALUE_REQUIRED,
'The path to security-checker'
)
->addOption(
'project',
null,
InputOption::VALUE_REQUIRED,
'The path to project'
)
->addOption(
'lock_path',
null,
InputOption::VALUE_REQUIRED,
'The composer.lock path relative to project',
'./composer.lock'
)
->addOption(
'output',
'o',
InputOption::VALUE_REQUIRED,
'The path to output file'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$securityChecker = $this->getApplication()->getSecurityChecker();
if ($input->getOption('checker')) {
$securityChecker = $input->getOption('checker');
}
if (!file_exists($securityChecker) && $securityChecker !== SecurityCheckService::INTERNAL) {
throw new \Exception('Invalid security-checker path '.$securityChecker, 1);
}
$service = new \Mactronique\CUA\Service\SecurityCheckService($securityChecker);
$projects = $this->getApplication()->getProjects();
//Chargement du projet via la ligne de commande
if ($input->getOption('project')) {
if (null === $input->getArgument('name')) {
throw new \Exception('Please set the name of project', 1);
}
$projects = [$input->getArgument('name') => ['path' => $input->getOption('project'), 'check_security'=> true, 'lock_path'=>$input->getOption('lock_path'), 'php_path'=>'php']];
}
//Pas de fichier de config donc fichier de sortie obligatoire
if ($input->hasParameterOption(['--no-config'], true) && null === $input->getOption('output')) {
throw new \Exception('Please set the output file option -o or --output', 1);
}
$outputFile = $input->getOption('output');
$installedService = new \Mactronique\CUA\Service\InstalledLibraryService();
foreach ($projects as $projectName => $projectConf) {
$projectPath = $projectConf['path'];
$lockPath = $projectConf['lock_path'];
$output->writeln(sprintf('Check Security <info>%s</info> at <comment>%s</comment>', $projectName, $projectPath));
if (!$projectConf['check_security']) {
$output->writeln('<info>Skip</info>');
continue;
}
if (isset($projectConf['private_dependencies']) && count($projectConf['private_dependencies'])) {
$tmpPath = tempnam(sys_get_temp_dir(), 'cua');
$lock = file_get_contents($projectPath.'/'.$lockPath);
foreach ($projectConf['private_dependencies'] as $libraryName) {
$findName = sprintf('"%s"', $libraryName);
if (false === $pos = mb_strpos($lock, $findName)) {
$output->writeln('Private dependency not found in composer.lock : <info>'.$findName.'</info> ');
continue;
}
$result = mb_ereg_replace(''.$findName.'', sprintf('"%s"', md5($libraryName.uniqid())), $lock);
if ($result === false) {
$output->writeln('<error>Error in remplacement of private library</error>');
continue;
}
if (false !== $pos = mb_strpos($result, $findName)) {
$output->writeln('<error>Error : The new lock file contains always the replaced private library '.$findName.'</error>');
}
$lock = $result;
}
file_put_contents($tmpPath, $lock);
$lockPath = $tmpPath;
$output->writeln('Replace lock file by modified version : <info>'.$lockPath.'</info>');
}
$resultProject = $service->checkSecurity($projectPath, $lockPath, $projectConf['php_path']);
if (isset($tmpPath)) {
@unlink($tmpPath);
}
if ($resultProject['error'] != '') {
$output->writeln(sprintf('<error> %s </error>', $resultProject['error']));
continue;
}
$output->writeln(sprintf(
'Result <error> %d </error> dependency with security issue',
count($resultProject['result'])
));
$this->getApplication()->setProjectSecurityResult($projectName, $resultProject['result']);
$this->getApplication()->saveSecurityResult($outputFile);
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
class ProjectListCommand extends Command
{
protected function configure()
{
$this
->setName('project:list')
->setDescription('Get configured project list')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$projects = $this->getApplication()->getProjects();
//Pas de fichier de config donc fichier de sortie obligatoire
if ($input->hasParameterOption(['--no-config'], true)) {
throw new \Exception('Unable to get the project list without configuration', 1);
}
$outputStyle = new OutputFormatterStyle('red', 'yellow', array());
$output->getFormatter()->setStyle('caution', $outputStyle);
$table = new Table($output);
$table->setHeaders(['Project Name', 'Location', 'Check Dependencies', 'Check Security', 'PHP Bin', 'composer.lock path']);
foreach ($projects as $projectName => $projectConf) {
$projectPath = $projectConf['path'];
if (!is_dir($projectPath)) {
$projectPath = '<error>'.$projectPath.'</error>';
$lock_path = '<caution>'.$projectConf['lock_path'].'</caution>';
} else {
$lock_path = realpath($projectConf['path']).'/'.$projectConf['lock_path'];
$lock_path = file_exists($lock_path) ? $projectConf['lock_path'] : '<error>'.$projectConf['lock_path'].'</error>';
}
$table->addRow([$projectName, $projectPath, $projectConf['check_dependencies'] ? 'true':'<error>false</error>', $projectConf['check_security'] ? 'true':'<error>false</error>', $projectConf['php_path'], $lock_path]);
}
$table->render();
}
}

View File

@@ -22,14 +22,6 @@ class MainConfiguration implements ConfigurationInterface
// ... add node definitions to the root of the tree
$rootNode
->children()
->arrayNode('projects')
->isRequired()
->normalizeKeys(false)
->cannotBeEmpty()
->useAttributeAsKey('name')
->prototype('scalar')
->end()
->end()
->arrayNode('persistance')
->addDefaultsIfNotSet()
->children()
@@ -44,6 +36,23 @@ class MainConfiguration implements ConfigurationInterface
->scalarNode('composer_path')
->defaultValue('/usr/bin/composer')
->end()
->scalarNode('security_checker_path')
->defaultValue('/usr/bin/security-checker')
->end()
->arrayNode('project_provider')
->addDefaultsIfNotSet()
->children()
->enumNode('type')
->defaultValue('file')
->values(array('file', 'redmine'))
->end()
->arrayNode('parameters')
->defaultValue([])
->prototype('variable')
->end()
->end()
->end()
->end()
->end()
;

View File

@@ -0,0 +1,62 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\Configuration;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class ProjectConfiguration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('main');
// ... add node definitions to the root of the tree
$rootNode
->children()
->arrayNode('projects')
->normalizeKeys(false)
->useAttributeAsKey('name')
->prototype('array')
->addDefaultsIfNotSet()
->children()
->scalarNode('path')
->cannotBeEmpty()
->end()
->scalarNode('lock_path')
->defaultValue('./composer.lock')
->end()
->booleanNode('check_dependencies')
->defaultTrue()
->end()
->booleanNode('check_security')
->defaultFalse()
->end()
->scalarNode('php_path')
->defaultValue('php')
->end()
->arrayNode('private_dependencies')
->prototype('scalar')
->end()
->end()
->enumNode('private_dependencies_strategy')
->defaultValue('remove')
->values(array('remove', 'hash'))
->end()
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}

View File

@@ -11,8 +11,10 @@
namespace Mactronique\CUA;
use Symfony\Component\Console\Application;
use Mactronique\CUA\Command\CheckCommand;
use Mactronique\CUA\Configuration\MainConfiguration;
use Mactronique\CUA\Command\CheckDependenciesCommand;
use Mactronique\CUA\Command\CheckSecurityCommand;
use Mactronique\CUA\Command\ProjectListCommand;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
@@ -28,12 +30,18 @@ class CuaApplication extends Application
private $persistance;
private $results;
private $projectsProvider;
private $results = [];
private $securityResults = [];
public function __construct()
{
parent::__construct('Composer Update Analyser', '1.0.0-alpha-3');
$this->add(new CheckCommand());
parent::__construct('Composer Update Analyser', '1.1.0');
$this->add(new CheckDependenciesCommand());
$this->add(new CheckSecurityCommand());
$this->add(new ProjectListCommand());
}
/**
@@ -84,7 +92,7 @@ class CuaApplication extends Application
public function getProjects()
{
return $this->config['projects'];
return $this->projectsProvider->getProjects();
}
public function setProjectResult($projectName, array $content)
@@ -105,6 +113,21 @@ class CuaApplication extends Application
return $this->config['composer_path'];
}
public function setProjectSecurityResult($projectName, array $content)
{
$this->securityResults[$projectName] = $content;
}
public function saveSecurityResult($path = null)
{
$this->persistance->saveSecurity($this->securityResults, $path);
}
public function getSecurityChecker()
{
return $this->config['security_checker_path'];
}
public function definePersistance($name, $parameters)
{
$className = 'Mactronique\\CUA\\Persistence\\'.$name;
@@ -114,6 +137,15 @@ class CuaApplication extends Application
$this->persistance = new $className($parameters);
}
protected function defineProjectProvider($config)
{
$className = 'Mactronique\\CUA\\ProjectProvider\\'.ucfirst($config['type']).'Provider';
if (!class_exists($className)) {
throw new \Exception('Unable to load this project provider class: '.$className, 1);
}
$this->projectsProvider = new $className($config['parameters']);
}
/**
* Gets the default input definition.
*
@@ -132,13 +164,15 @@ class CuaApplication extends Application
*/
private function boot(InputInterface $input)
{
$this->config = ['output' => null, 'projects' => [], 'composer_path' => null];
if (!$input->hasParameterOption(['--no-config'], true)) {
$configFile = __DIR__.'/cua.yml';
$this->loadConfigurationFile($configFile);
$this->definePersistance($this->config['persistance']['format'], $this->config['persistance']['parameters']);
$this->config = ['output' => null, 'projects' => [], 'composer_path' => null, 'security_checker_path'=> null];
if ($input->hasParameterOption(['--no-config'], true)) {
return;
}
$configFile = __DIR__.'/../cua.yml';
$this->loadConfigurationFile($configFile);
$this->definePersistance($this->config['persistance']['format'], $this->config['persistance']['parameters']);
$this->defineProjectProvider($this->config['project_provider']);
}
/**

View File

@@ -10,7 +10,7 @@
namespace Mactronique\CUA\Persistence;
class DbalPersistance implements Persistence
class DbalPersistence implements Persistence
{
/**
* @var string default plath of file
@@ -53,6 +53,52 @@ class DbalPersistance implements Persistence
}
}
public function saveSecurity(array $content, array $config = null)
{
$this->connexion = \Doctrine\DBAL\DriverManager::getConnection($this->config);
$this->connexion->connect();
foreach ($content as $project => $securitydata) {
$data = ['state' => 'fixed'];
$data['updated_at'] = new \DateTime();
$this->connexion->update($this->config['table_name_security'], $data, ['project' => $project, 'state' => 'open'], ['string', 'datetime', 'string', 'string']);
if (count($securitydata) === 0) {
continue;
}
foreach ($securitydata as $library => $infos) {
$list = json_encode($infos['advisories']);
if ($this->checkSecurityExist($project, $library, $infos['version'])) {
$this->connexion->update(
$this->config['table_name_security'],
[
'details' => $list,
'state' => 'open',
'updated_at' => new \DateTime(),
],
['project' => $project, 'library'=>$library, 'version'=>$infos['version']],
['string', 'string', 'datetime', 'string', 'string', 'string']
);
} else {
$this->connexion->insert(
$this->config['table_name_security'],
[
'details' => $list,
'state' => 'open',
'updated_at' => new \DateTime(),
'project' => $project,
'library'=>$library,
'version'=>$infos['version']
],
['string', 'string', 'datetime', 'string', 'string', 'string']
);
}
}
}
}
/**
* Manage the installed library.
*
@@ -186,4 +232,13 @@ class DbalPersistance implements Persistence
$this->connexion->update($this->config['table_name'], $data, ['project' => $project], ['string', 'string', 'string', 'datetime', 'string']);
}
private function checkSecurityExist($project, $library, $version)
{
$result = $this->connexion->executeQuery('SELECT count(*) as nombre FROM '.$this->config['table_name_security'].' WHERE project= ? AND library=? AND version=?', [$project, $library, $version], ['string', 'string', 'string']);
$nb = $result->fetch();
return $nb['nombre'] != 0;
}
}

View File

@@ -17,4 +17,10 @@ interface Persistence
* @param array $config custom config
*/
public function save(array $content, array $config = null);
/**
* @param array $content Content to save
* @param array $config custom config
*/
public function saveSecurity(array $content, array $config = null);
}

View File

@@ -10,7 +10,7 @@
namespace Mactronique\CUA\Persistence;
class RedmineCuaPersistance implements Persistence
class RedmineCuaPersistence implements Persistence
{
/**
* @var string default plath of file
@@ -40,6 +40,8 @@ class RedmineCuaPersistance implements Persistence
public function save(array $content, array $config = null)
{
$this->connexion = \Doctrine\DBAL\DriverManager::getConnection($this->config);
//Debug des requettes SQL :
//$this->connexion->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
$this->connexion->connect();
foreach ($content as $key => $data) {
@@ -57,6 +59,55 @@ class RedmineCuaPersistance implements Persistence
}
}
public function saveSecurity(array $content, array $config = null)
{
$this->connexion = \Doctrine\DBAL\DriverManager::getConnection($this->config);
$this->connexion->connect();
foreach ($content as $project => $securitydata) {
$projectId = $this->convertProjectCode($project);
$data = ['state' => 'fixed'];
$data['updated_at'] = new \DateTime();
$this->connexion->update($this->config['table_name_security'], $data, ['project_id' => $projectId, 'state' => 'open'], ['string', 'datetime', 'integer', 'string']);
if (count($securitydata) === 0) {
continue;
}
foreach ($securitydata as $library => $infos) {
dump($library, $infos);
$list = json_encode($infos['advisories']);
if ($this->checkSecurityExist($projectId, $library, $infos['version'])) {
$this->connexion->update(
$this->config['table_name_security'],
[
'details' => $list,
'state' => 'open',
'updated_at' => new \DateTime(),
],
['project_id' => $projectId, 'library'=>$library, 'version'=>$infos['version']],
['string', 'string', 'datetime', 'integer', 'string', 'string']
);
} else {
$this->connexion->insert(
$this->config['table_name_security'],
[
'details' => $list,
'state' => 'open',
'updated_at' => new \DateTime(),
'project_id' => $projectId,
'library'=>$library,
'version'=>$infos['version']
],
['string', 'string', 'datetime', 'integer', 'string', 'string']
);
}
}
}
}
/**
* Manage the installed library.
*
@@ -138,16 +189,24 @@ class RedmineCuaPersistance implements Persistence
private function abandonedLib($project, array $abandonned)
{
foreach ($abandonned as $key => $library) {
$this->connexion->update($this->config['table_name'], ['deprecated' => true], ['project_id' => $project, 'library' => $library], ['boolean', 'integer', 'string']);
$this->connexion->update($this->config['table_name'], ['deprecated' => true], ['project_id' => $project, 'library' => $library], ['deprecated' => 'boolean', 'project_id' => 'integer', 'library' => 'string']);
}
}
private function checkExist($project, $library)
{
$result = $this->connexion->executeQuery('SELECT count(*) as nombre FROM '.$this->config['table_name'].' WHERE project_id= ? AND library=?', [$project, $library], ['integer', 'string']);
$nb = $result->fetch();
$nb = $result->fetch(\PDO::FETCH_ASSOC);
return $nb['nombre'] != 0;
return intval($nb['nombre']) != 0;
}
private function checkSecurityExist($project, $library, $version)
{
$result = $this->connexion->executeQuery('SELECT count(*) as nombre FROM '.$this->config['table_name_security'].' WHERE project_id= ? AND library=? AND version=?', [$project, $library, $version], ['integer', 'string', 'string']);
$nb = $result->fetch(\PDO::FETCH_ASSOC);
return intval($nb['nombre']) != 0;
}
/**
@@ -158,7 +217,19 @@ class RedmineCuaPersistance implements Persistence
$data['deprecated'] = false;
$data['updated_at'] = new \DateTime();
$this->connexion->insert($this->config['table_name'], $data, ['integer', 'string', 'string', 'string', 'string', 'string', 'boolean', 'datetime']);
$this->connexion->insert(
$this->config['table_name'],
$data,
[
'project_id' => 'integer',
'library' => 'string',
'version' => 'string',
'state' => 'string',
'to_library' => 'string',
'to_version' => 'string',
'deprecated' => 'boolean',
'updated_at' => 'datetime',
]);
}
/**
@@ -175,7 +246,20 @@ class RedmineCuaPersistance implements Persistence
unset($data['deprecated']);
}
$this->connexion->update($this->config['table_name'], $data, $key, ['string', 'string', 'string', 'string', 'datetime', 'string', 'integer']);
$this->connexion->update(
$this->config['table_name'],
$data,
$key,
[
'version' => 'string',
'state' => 'string',
'to_library' => 'string',
'to_version' => 'string',
'updated_at' => 'datetime',
'library' => 'string',
'project_id' => 'integer'
]
);
}
/**

View File

@@ -19,9 +19,13 @@ use Symfony\Component\Yaml\Yaml;
class YamlFile implements Persistence
{
/**
* @var string default plath of file
* @var string default path of file
*/
private $filePath;
/**
* @var string default path of file
*/
private $fileSecurityPath;
/**
* @param array $config
@@ -29,6 +33,7 @@ class YamlFile implements Persistence
public function __construct(array $config)
{
$this->filePath = $config['path'];
$this->fileSecurityPath = $config['path_security'];
}
/**
@@ -38,6 +43,13 @@ class YamlFile implements Persistence
public function save(array $content, array $config = null)
{
$content = Yaml::dump($content, 100);
file_put_contents(($config !== null)? $config['path']:$this->filePath, $content);
file_put_contents(($config !== null && isset($config['path']))? $config['path']:$this->filePath, $content);
}
public function saveSecurity(array $content, array $config = null)
{
$content = Yaml::dump($content, 100);
file_put_contents(($config !== null && isset($config['path_security']))? $config['path_security']:$this->fileSecurityPath, $content);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\ProjectProvider;
use Symfony\Component\Config\Definition\Processor;
use Mactronique\CUA\Configuration\ProjectConfiguration;
class DbalProvider implements ProjectProviderInterface
{
private $config;
private $projects;
public function __construct($config)
{
if (null !== $config || !is_array($config)) {
throw new \Exception("The configuration is not set or not an array", 1);
}
$this->config = $config;
if (!isset($config['working_dir']) || !is_dir($config['working_dir'])) {
throw new \Exception("working_dir configuration key is not set or not exists", 1);
}
if (!isset($config['db']) || !is_array($config['db'])) {
throw new \Exception("db configuration key is not set or is not an array", 1);
}
if (!isset($config['table_name']) || !is_string($config['table_name'])) {
throw new \Exception("table_name configuration key is not set or is not a string", 1);
}
}
public function getProjects()
{
if (null !== $this->projects && is_array($this->projects)) {
return $this->projects;
}
$connexion = \Doctrine\DBAL\DriverManager::getConnection($this->config['db']);
$connexion->connect();
$config = [];
$result = $connexion->executeQuery(sprintf("SELECT code, name, path, check_dependencies, check_security, lock_path, php_path, private_dependencies FROM %s WHERE check_dependencies = '1' or check_security = '1'", $this->config['db']['table_name']));
while ($ligne = $result->fetch(\PDO::FETCH_ASSOC)) {
$conf = [
'path' => $this->config['working_dir'].DIRECTORY_SEPARATOR.$ligne['path'],
'check_dependencies' => boolval($ligne['check_dependencies']),
'check_security' => boolval($ligne['check_security']),
];
if (!empty($ligne['private_dependencies'])) {
$conf['private_dependencies'] = json_decode($ligne['private_dependencies'], true);
// JSON decode silent error
if ($conf['private_dependencies'] === false) {
$conf['private_dependencies'] = null;
}
}
if (!empty($ligne['lock_path'])) {
$conf['lock_path'] = $ligne['lock_path'];
}
if (!empty($ligne['php_path'])) {
$conf['php_path'] = $ligne['php_path'];
}
$config[$ligne['code']] = $conf;
}
$configs = [['projects' => $config]];
$processor = new Processor();
$configuration = new ProjectConfiguration();
$this->projects = $processor->processConfiguration($configuration, $configs)['projects'];
return $this->projects;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\ProjectProvider;
use Mactronique\CUA\Configuration\ProjectConfiguration;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Yaml\Yaml;
class FileProvider implements ProjectProviderInterface
{
private $config;
private $projects;
public function __construct($config)
{
$this->config = $config;
if (!isset($config['path'])) {
throw new \Exception("The path of file project is not set !", 1);
}
}
public function getProjects()
{
if (null !== $this->projects && is_array($this->projects)) {
return $this->projects;
}
$filePath = __DIR__.'/../../'.$this->config['path'];
if (!file_exists($filePath)) {
throw new \Exception("Unable to load file : ".$filePath, 1);
}
$config = Yaml::parse(file_get_contents($filePath));
$configs = [$config];
$processor = new Processor();
$configuration = new ProjectConfiguration();
$this->projects = $processor->processConfiguration($configuration, $configs)['projects'];
return $this->projects;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\ProjectProvider;
interface ProjectProviderInterface
{
/**
* return the list of project with configuration
* @return array
*/
public function getProjects();
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\ProjectProvider;
use Symfony\Component\Config\Definition\Processor;
use Mactronique\CUA\Configuration\ProjectConfiguration;
class RedmineProvider implements ProjectProviderInterface
{
private $config;
private $projects;
public function __construct($config)
{
$this->config = $config;
if (!isset($config['working_dir']) || !is_dir($config['working_dir'])) {
throw new \Exception("working_dir configuration key is not set or not exists", 1);
}
if (!isset($config['db']) || !is_array($config['db'])) {
throw new \Exception("db configuration key is not set or is not an array", 1);
}
}
public function getProjects()
{
if (null !== $this->projects && is_array($this->projects)) {
return $this->projects;
}
$connexion = \Doctrine\DBAL\DriverManager::getConnection($this->config['db']);
$connexion->connect();
$config = [];
$result = $connexion->executeQuery(sprintf("SELECT s.*, p.identifier as pid FROM `%s` s LEFT JOIN `projects` p ON s.project_id = p.id LEFT JOIN `repositories` r ON s.project_id = r.project_id WHERE p.status= '1' and r.is_default = '1'", $this->config['db']['table_name']));
while ($ligne = $result->fetch(\PDO::FETCH_ASSOC)) {
$conf = [
'path' => $this->config['working_dir'].'/'.$ligne['pid'],
'check_dependencies' => boolval($ligne['check_update']),
'check_security' => boolval($ligne['check_security']),
];
if (!empty($ligne['lock_path'])) {
$conf['lock_path'] = $ligne['lock_path'];
}
if (!empty($ligne['php_bin'])) {
$conf['php_path'] = $ligne['php_bin'];
}
$config[$ligne['pid']] = $conf;
}
$configs = [['projects' => $config]];
$processor = new Processor();
$configuration = new ProjectConfiguration();
$this->projects = $processor->processConfiguration($configuration, $configs)['projects'];
return $this->projects;
}
}

View File

@@ -18,6 +18,11 @@ use Symfony\Component\Process\Exception\ProcessTimedOutException;
class CheckUpdateService
{
/**
* @var LoggerInterface|NullLogger
*/
private $logger;
/**
* @var string
*/
@@ -33,7 +38,7 @@ class CheckUpdateService
$this->logger = ($logger === null)? new NullLogger():$logger;
}
public function checkComposerUpdate($projectPath)
public function checkComposerUpdate($projectPath, $php_path)
{
$resultProject = [
'install' => [],
@@ -43,7 +48,7 @@ class CheckUpdateService
'error' => '',
];
$process = new Process($this->composerPath.' update --dry-run --no-ansi');
$process = new Process($php_path.' '.$this->composerPath.' update --dry-run --no-ansi');
$process->setWorkingDirectory($projectPath);
$process->setTimeout(300);
try {

View File

@@ -38,6 +38,12 @@ class InstalledLibraryService
foreach ($json['packages'] as $value) {
$libraries[$value['name']] = $value['version'];
}
if (!isset($json['platform'])) {
continue;
}
foreach ($json['platform'] as $platform => $version) {
$libraries[$platform] = $version;
}
}
return $libraries;

View File

@@ -0,0 +1,127 @@
<?php
/**
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
namespace Mactronique\CUA\Service;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use SensioLabs\Security\SecurityChecker;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
class SecurityCheckService
{
const INTERNAL = 'internal';
/** @var string */
private $securityCheckPath;
/** @var LoggerInterface */
private $logger;
/**
* SecurityCheckService constructor.
* @param string $securityCheckPath
* @param LoggerInterface|null $logger
*/
public function __construct($securityCheckPath, LoggerInterface $logger = null)
{
$this->securityCheckPath = $securityCheckPath;
$this->logger = ($logger === null)? new NullLogger():$logger;
}
/**
* @param string $projectPath
* @param string $lockPath
* @param string $phpPath
* @return array
*/
public function checkSecurity($projectPath, $lockPath, $phpPath)
{
if (strtolower($this->securityCheckPath) === self::INTERNAL){
return $this->checkSecurityInt($projectPath, $lockPath);
}
return $this->checkSecurityExt($projectPath, $lockPath, $phpPath);
}
/**
* Check security with the integrated checker
* @param string $projectPath
* @param string $lockPath
* @return array
*/
private function checkSecurityInt($projectPath, $lockPath)
{
$resultProject = [
'error' => '',
'result' => [],
];
try {
$result = (string) (new SecurityChecker())->check($lockPath);
return $this->processJson($result, $resultProject, $projectPath);
} catch(\Exception $e) {
$resultProject['error'] = $e->getMessage();
}
return $resultProject;
}
/**
* Use external checker
* @param string $projectPath
* @param string $lockPath
* @param string $phpPath
* @return array
*/
private function checkSecurityExt($projectPath, $lockPath, $phpPath)
{
$resultProject = [
'error' => '',
'result' => [],
];
$process = new Process($phpPath.' '.$this->securityCheckPath.' security:check '.$lockPath.' --format=json ');
$process->setWorkingDirectory($projectPath);
$process->setTimeout(3000);
try {
$returnCode = $process->run();
$sortieErr = $process->getErrorOutput();
if (strlen($sortieErr) > 0) {
$resultProject['error'] = 'Result code' . $returnCode. "\n" . $sortieErr;
}
return $this->processJson($process->getOutput(), $resultProject, $projectPath);
} catch (ProcessTimedOutException $e) {
$this->logger->error('Process time out', ['projectPath'=>$projectPath, 'exception'=>$e]);
$resultProject['error'] = 'Time out '.$e->getMessage();
return $resultProject;
}
}
/**
* @param string $jsonString
* @param array $resultProject
* @param string $projectPath
* @return mixed
*/
private function processJson($jsonString, $resultProject, $projectPath)
{
$datas = json_decode($jsonString, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->logger->error('Json decode error', ['projectPath'=>$projectPath, 'json_error'=>json_last_error_msg()]);
$resultProject['error'] .= 'Json decode error '.json_last_error_msg();
return $resultProject;
}
$resultProject['result'] = $datas;
return $resultProject;
}
}

View File

@@ -0,0 +1,78 @@
/*
* This file is part of Composer Update Analyser package.
*
* @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
* @copyright 2016-2019 - Jean-Baptiste Nahan
* @license MIT
*/
--- FOR MSSQL
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
if not exists (select * from sysobjects where name='dependencies' and xtype='U')
CREATE TABLE [dbo].[dependencies](
[id] [int] IDENTITY(1,1) NOT NULL,
[project] [nvarchar](50) NOT NULL,
[library] [nvarchar](250) NOT NULL,
[version] [nvarchar](250) NOT NULL,
[state] [nvarchar](20) NOT NULL,
[to_library] [nvarchar](250) DEFAULT NULL,
[to_version] [nvarchar](250) DEFAULT NULL,
[deprecated] [bit] DEFAULT NULL,
[updated_at] [datetime2](7) NOT NULL,
CONSTRAINT [PK_dependencies] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
if not exists (select * from sysobjects where name='idx_proj_lib' and xtype='UQ')
ALTER TABLE [dbo].[dependencies] ADD CONSTRAINT [idx_proj_lib] UNIQUE NONCLUSTERED
(
[project] ASC,
[library] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
if not exists (select * from sysobjects where name='security' and xtype='U')
CREATE TABLE [dbo].[security](
[id] [int] IDENTITY(1,1) NOT NULL,
[project] [nvarchar](50) NOT NULL,
[library] [nvarchar](255) NOT NULL,
[version] [nvarchar](255) NOT NULL,
[state] [nvarchar](20) NOT NULL,
[details] [text] NOT NULL,
[updated_at] [datetime2](7) NOT NULL,
CONSTRAINT [PK_security] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
if not exists (select * from sysobjects where name='projects' and xtype='U')
CREATE TABLE [dbo].[projects] (
[code] [nvarchar](10) NOT NULL,
[name] [nvarchar](100) NOT NULL,
[path] [nvarchar](255) NOT NULL,
[lock_path] [nvarchar](255) NOT NULL,
[php_path] [nvarchar](255) NOT NULL,
[private_dependencies] [nvarchar](max) DEFAULT NULL,
[private_dependencies_strategy] [nvarchar](10) DEFAULT 'remove',
[check_dependencies] [bit] DEFAULT NULL,
[check_security] [bit] DEFAULT NULL,
[updated_at] [datetime2](7) NOT NULL
CONSTRAINT [PK_project] PRIMARY KEY CLUSTERED
(
[code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

View File

@@ -0,0 +1,66 @@
##
## This file is part of Composer Update Analyser package.
##
## @author Jean-Baptiste Nahan <814683+macintoshplus@users.noreply.github.com>
## @copyright 2016-2019 - Jean-Baptiste Nahan
## @license MIT
##
## FOR MySQL
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS `dependencies` (
`id` int(11) NOT NULL,
`project` varchar(50) NOT NULL,
`library` varchar(250) NOT NULL,
`version` varchar(250) NOT NULL,
`state` varchar(20) NOT NULL,
`to_library` varchar(250) DEFAULT NULL,
`to_version` varchar(250) DEFAULT NULL,
`deprecated` tinyint(1) DEFAULT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `dependencies`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `idx_proj_lib` (`project`,`library`(191)) USING BTREE,
ADD KEY `idx_plv` (`project`,`library`(191),`version`(191));
ALTER TABLE `dependencies`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
CREATE TABLE IF NOT EXISTS `security` (
`id` int(11) NOT NULL,
`project` varchar(50) NOT NULL,
`library` varchar(255) NOT NULL,
`version` varchar(255) NOT NULL,
`state` varchar(20) NOT NULL,
`details` text NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `security`
ADD PRIMARY KEY (`id`);
ALTER TABLE `security`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
CREATE TABLE IF NOT EXISTS `projects` (
`code` varchar(10) NOT NULL,
`name` varchar(100) NOT NULL,
`path` varchar(255) NOT NULL,
`lock_path` varchar(255) NOT NULL,
`php_path` varchar(255) NOT NULL,
`private_dependencies` text DEFAULT NULL,
`private_dependencies_strategy` varchar(10) DEFAULT 'remove',
`check_dependencies` tinyint(1) DEFAULT NULL,
`check_security` tinyint(1) DEFAULT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `projects`
ADD PRIMARY KEY (`code`);

View File

@@ -0,0 +1,45 @@
CREATE TABLE "dependencies" (
"id" serial NOT NULL PRIMARY KEY,
"project" varchar(50) NOT NULL,
"library" varchar(250) NOT NULL,
"version" varchar(250) NOT NULL,
"state" varchar(20) NOT NULL,
"to_library" varchar(250) NULL,
"to_version" varchar(250) NULL,
"deprecated" boolean NULL,
"updated_at" timestamp NOT NULL
);
CREATE UNIQUE INDEX CONCURRENTLY idx_proj_lib
ON dependencies (project, library);
ALTER TABLE dependencies
ADD CONSTRAINT unique_proj_lib
UNIQUE USING INDEX idx_proj_lib;
CREATE INDEX idx_plv
ON dependencies (project, library, version);
CREATE TABLE "security" (
"id" serial NOT NULL PRIMARY KEY,
"project" varchar(50) NOT NULL,
"library" varchar(250) NOT NULL,
"version" varchar(250) NOT NULL,
"state" varchar(20) NOT NULL,
"details" text NOT NULL,
"updated_at" timestamp NOT NULL
);
CREATE TABLE "projects" (
"code" varchar(10) NOT NULL PRIMARY KEY,
"name" varchar(100) NOT NULL,
"path" varchar(255) NOT NULL,
"lock_path" varchar(255) NOT NULL,
"php_path" varchar(255) NOT NULL,
"private_dependencies" text DEFAULT NULL,
"private_dependencies_strategy" varchar(10) DEFAULT 'remove',
"check_dependencies" boolean DEFAULT NULL,
"check_security" boolean DEFAULT NULL,
"updated_at" timestamp NOT NULL
);

View File

@@ -0,0 +1,36 @@
CREATE TABLE "dependencies" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"project" TEXT NOT NULL,
"library" TEXT NOT NULL,
"version" TEXT NOT NULL,
"state" TEXT NOT NULL,
"to_library" TEXT,
"to_version" TEXT,
"deprecated" INTEGER,
"updated_at" TEXT NOT NULL
);
CREATE TABLE "security" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"projet" TEXT NOT NULL,
"library" TEXT NOT NULL,
"version" TEXT NOT NULL,
"state" TEXT NOT NULL,
"details" TEXT NOT NULL,
"updated_at" TEXT NOT NULL
);
CREATE TABLE "projects" (
"code" TEXT NOT NULL,
"name" TEXT NOT NULL,
"path" TEXT NOT NULL,
"lock_path" TEXT NOT NULL,
"php_path" TEXT NOT NULL,
"private_dependencies" TEXT,
"private_dependencies_strategy" TEXT DEFAULT 'remove',
"check_dependencies" INTEGER,
"check_security" INTEGER,
"updated_at" TEXT NOT NULL,
PRIMARY KEY("code")
);

View File

@@ -14,7 +14,7 @@ class YamlFile extends atoum
{
public function testInit()
{
$this->newTestedInstance(['path'=>'here']);
$this->newTestedInstance(['path'=>'here', 'path_security'=>'here_security']);
$this->assert('type')->object($this->testedInstance)->isInstanceOf('Mactronique\CUA\Persistence\Persistence');
}
}