Compare commits

...
Sign in to create a new pull request.

11 commits

Author SHA1 Message Date
Nicolas Demangeat
433d13e4c0 fix review 2023-10-13 12:29:17 +02:00
Jérémy Munsch
af5dea4e0d
Add CD Files 2023-10-13 11:57:18 +02:00
Nicolas Demangeat
6df8c0373c fix phpcs 2023-10-12 15:13:58 +02:00
Nicolas Demangeat
897cb081d7 bugs fix 2023-09-26 10:45:59 +02:00
Nicolas Demangeat
2cee2f5f06 update to retrive token from header request 2023-09-25 15:53:06 +02:00
Nicolas Demangeat
cc9567da27 wip 2023-09-22 17:19:16 +02:00
Nicolas Demangeat
601823ef58 refix web-bo 2023-09-22 10:18:17 +02:00
Nicolas Demangeat
ce0a00012e fix web-bo 2023-09-22 10:16:25 +02:00
Nicolas Demangeat
580be2bf99 bump laravel 10 and add networks model 2023-09-22 09:59:11 +02:00
Vincent BRIDIER
ddeda94c45 Merge branch 'encryptData' into 'develop'
Encrypt sensitive Data

See merge request service-web-lab/ldc-web!4
2023-01-26 09:27:55 +00:00
Jérémy Munsch
fa8810881c
Encrypt sensitive Data 2022-12-22 10:36:32 +01:00
403 changed files with 156956 additions and 22656 deletions

5
.browserslistrc Normal file
View file

@ -0,0 +1,5 @@
# Browsers that we support
last 3 version
> 0.3%
not dead # no browsers without security updates

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
.git
*.md

View file

@ -3,9 +3,9 @@ root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
@ -13,3 +13,9 @@ trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4
[*.vue]
indent_size = 2

View file

@ -5,6 +5,8 @@ APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
@ -15,36 +17,53 @@ DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
MAIL_HANDLER=
SAML2_ldc_IDP_HOST=
SAML2_ldc_SP_x509=
SAML2_ldc_SP_PRIVATEKEY=
SAML2_ldc_IDP_ENTITYID=
SAML2_ldc_IDP_SSO_URL=
SAML2_ldc_IDP_SL_URL=
SAML2_ldc_IDP_x509=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
MAILJET_APIKEY=
MAILJET_APISECRET=
MAILJET_SMSTOKEN=
MAILJET_SMSENDPOINT=
MAILJET_SMSFROM=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
WEBHOOKS_GITLAB_NOTIFY=
WEBHOOKS_GITLAB_TOKEN=
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MEMCACHED_HOST=
MEMCACHED_PORT=
# Sentry
SENTRY_LARAVEL_DSN=
# ADMIN SECURE
# - API ENDPOINT (Optional)
LDC_WEB_API_ENDPOINT="https://ldc-web.ldc.fr/api/ldc-networks"
# - API KEY
LDC_WEB_API_KEY=""
# - LIST SEPARATED WITH ',' (Optional)
LDC_WEB_CIDR_MANUAL_ALLOWED_LIST=""

55
.eslintrc.json Normal file
View file

@ -0,0 +1,55 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:vue/vue3-strongly-recommended",
"@vue/eslint-config-typescript"
],
"overrides": [
{
"files": ["*.ts", "*.vue"],
"rules": {
"no-undef": "off"
}
},
{
"files": ["*.vue"],
"rules": {
"indent": "off"
}
}
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".vue", ".ts", ".d.ts"]
},
"alias": {
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
"map": [
["~", "./resources/assets"],
["@", "./node_modules"]
]
}
}
}
}

14
.gitattributes vendored
View file

@ -1,5 +1,11 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

38
.gitignore vendored
View file

@ -1,31 +1,23 @@
/node_modules
/public/hot
/public/storage
/public/css
/public/js
/public/images
/public/fonts
/public/medias
/public/uploads
/public/assets
/public/cache
/public/mix-manifest.json
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
.stylelintcache
/.fleet
/.idea
/.phpunit.cache
/.vscode
/node_modules
/public/build
/public/cache
/public/hot
/public/sitemap.xml
/public/storage
/storage/*.key
/vendor
auth.json
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/.idea
/.vscode
/public/.htpasswd
/public/.htaccess
/resourcesLegacy

View file

@ -1,54 +1,23 @@
include:
- project: "ldc-web/shared-ci"
ref: "main"
file: "lint/lint.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "security/security.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "build/build.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "test/test.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "deploy/deploy.yml"
stages:
- Security
- CodingStandards
composer-security:
stage: Security
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
script:
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'composer-security-checker'
paths:
- composer-security-checker-report.json
allow_failure: true
needs: []
npm-security:
stage: Security
image: node:17.9.1
before_script:
- npm install --location=global audit-ci@^6
script:
- audit-ci -p npm -o json > npm-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'npm-security-checker'
paths:
- npm-security-checker-report.json
allow_failure: true
needs: []
phpcs:
stage: CodingStandards
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
before_script:
- composer config gitlab-token.gitlab.com $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
- composer install
cache:
paths:
- vendor/
script:
- vendor/bin/phpcs --report=junit --report-file=phpcs-report.xml
artifacts:
when: on_failure
expose_as: 'phpcs'
paths:
- phpcs-report.xml
reports:
junit: phpcs-report.xml
allow_failure: false
needs:
- composer-security
- npm-security
- lint
- security
- build
- test
- deploy

View file

@ -0,0 +1,11 @@
## Comportement constaté
## Comportement attendu
## Solution envisagée
## Étapes pour la reproduction du bug
*

View file

@ -0,0 +1,7 @@
## Présentation du besoin
## Implémentation
## Maquettes, documentations...

View file

@ -1,13 +0,0 @@
php:
preset: laravel
disabled:
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

43
.stylelintrc.json Normal file
View file

@ -0,0 +1,43 @@
{
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"plugins": ["stylelint-scss"],
"files": ["**/*.css", "**/*.scss"],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
"**/vendor/*.scss",
"**/vendor/**/*.scss",
"**/vendor/*.sass",
"**/vendor/**/*.sass",
"**/vendor/*.less",
"**/vendor/**/*.less"
],
"customSyntax": "postcss-scss",
"rules": {
"indentation": 4,
"string-quotes": "double",
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"declaration-colon-space-after": "always-single-line",
"function-parentheses-space-inside": "never-single-line",
"declaration-colon-newline-after": null,
"value-list-comma-newline-after": null,
"scss/double-slash-comment-empty-line-before": [
"always",
{
"ignore": [
"between-comments",
"stylelint-commands",
"inside-block"
]
}
]
}
}

39
.vscode/settings.json vendored
View file

@ -1,23 +1,26 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#385567",
"activityBar.activeBorder": "#b06e97",
"activityBar.background": "#385567",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#b06e97",
"activityBar.activeBackground": "#e4f100",
"activityBar.activeBorder": "#1a1d02",
"activityBar.background": "#e4f100",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#00b5bf",
"activityBarBadge.foreground": "#15202b",
"sash.hoverBorder": "#385567",
"statusBar.background": "#263a46",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#385567",
"statusBarItem.remoteBackground": "#263a46",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#263a46",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#263a4699",
"titleBar.inactiveForeground": "#e7e7e799",
"commandCenter.border": "#e7e7e799"
"sash.hoverBorder": "#e4f100",
"statusBar.background": "#b4be00",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#848b00",
"statusBarItem.remoteBackground": "#b4be00",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#b4be00",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#b4be0099",
"titleBar.inactiveForeground": "#15202b99",
"commandCenter.border": "#15202b99"
},
"peacock.remoteColor": "#263a46"
"peacock.remoteColor": "#B4BE00",
"intelephense.environment.phpVersion": "8.2.0",
"intelephense.completion.fullyQualifyGlobalConstantsAndFunctions": true,
"intelephense.phpdoc.useFullyQualifiedNames": true
}

5
Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest
COPY --chown=www-data:www-data . /app/srcs
USER root
RUN chmod -R 755 /app/srcs/storage
USER www-data

View file

@ -1,12 +1,66 @@
# LDC WEB
## Installation
```
cp .env.example .env
php artisan key:generate
```
## Front
```
npm install && npm run prod
```
## Dependencies
- Memcached
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Cubet Techno Labs](https://cubettech.com)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[Many](https://www.many.co.uk)**
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
- **[DevSquad](https://devsquad.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[OP.GG](https://op.gg)**
- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
- **[Lendio](https://lendio.com)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View file

@ -7,9 +7,6 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
/**
* Creates a new user
*/
class CreateAdmin extends Command
{
/**
@ -26,92 +23,155 @@ class CreateAdmin extends Command
*/
protected $description = 'Create a backend admin user';
/**
* The firstname of the new user.
*
* @var string
*/
protected $firstname;
/**
* The lastname of the new user.
*
* @var string
*/
protected $lastname;
/**
* The email of the new user.
*
* @var string
*/
protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
public function handle(): int
{
$this->firstname = null;
$this->addFirstname();
$email = $this->getInput(
'Type the wanted user email',
'email',
'required|ends_with:@ldc.fr|unique:users,email|email:rfc,dns',
'equipe.web@ldc.fr'
);
$this->lastname = null;
$this->addLastname();
$firstname = $this->getInput(
'Type the wanted user firstname',
'firstname',
'required|min:5,max:255',
'Saitama'
);
$this->email = null;
$this->addEmail();
$lastname = $this->getInput(
'Type the wanted user lastname',
'lastname',
'required|min:5,max:255',
'Hagemanto'
);
$this->password = null;
$this->addPassword();
$phone = $this->getInput(
'Type the wanted user phone',
'phone',
'required|phone:FR|unique:users,phone|min:5,max:255'
);
$password = null;
while (
($password = $this->secret(
(!\is_null($password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
};
$admin = new User([
'firstname' => $firstname,
'lastname' => $lastname,
'email' => $email,
'phone' => $phone,
'password' => $password,
'equipe_web_member' => $this->confirm('Is the user an member of equipe web ?')
$user = new User([
'firstname' => $this->firstname,
'lastname' => $this->lastname,
'email' => $this->email,
'password' => $this->password,
]);
$admin->saveOrFail();
$user->saveOrFail();
$this->info('User created 👌');
$this->info('User created.');
return 0;
}
/**
* Get User Input
* Add a firstname to the user.
*
* @param string $message
* @param string $inputName
* @param string|array $rules
* @param string|null $defaultValue
* @return mixed
* @return void
*/
private function getInput(string $message, string $inputName, $rules, ?string $defaultValue = null)
private function addFirstname(): void
{
$out = null;
while (is_null($out)) {
while (is_null($this->firstname)) {
try {
$tmp = $this->ask($message, $defaultValue);
$validator = Validator::make([$inputName => $tmp], [
$inputName => $rules
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255'
]);
$validator->validate();
$out = $tmp;
$this->firstname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
"'Please provide a valid $inputName %s'",
'Please provide a valid firstname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
return $out;
} //end try
}; //end while
}
/**
* Add a lastname to the user.
*
* @return void
*/
private function addLastname(): void
{
while (is_null($this->lastname)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->lastname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
}

View file

@ -1,183 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\LDCNetwork;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage;
class ExportDatas extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'datas:export';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Export data in Excel format';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
$this->exportProjects("EDL_WEB.csv");
$this->exportServers("EDL_Servers.csv");
$this->exportLdcNetworks("EDL_LDC_Networks.csv");
return 0;
}
/**
* Export ldc networks as filename.csv
*
* @param string $filename
* @return void
*/
private function exportLdcNetworks(string $filename)
{
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\LDCNetwork> */
$networks = LDCNetwork::all();
Storage::disk('export')->append($filename, "CIDR;First IP;Last IP");
foreach ($networks as $network) {
$infos = [
trim($network->cidr),
trim($network->first_ip),
trim($network->last_ip),
];
Storage::disk('export')->append($filename, implode(";", $infos));
}
}
/**
* Export servers as filename.csv
*
* @param string $filename
* @return void
*/
private function exportServers(string $filename)
{
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Server> */
$servers = Server::orderBy('env', 'DESC')->get();
Storage::disk('export')->append(
$filename,
"ENV;NOM;TITRE;FUNCTION;SSH;IP INFRAWEB;VIRTUAL IP;COMMENTAIRE"
);
foreach ($servers as $server) {
$infos = [
trim($server->env),
trim($server->name),
trim($server->title),
trim($server->function),
trim($server->ssh),
trim($server->infraweb_ip),
trim($server->virtual_ip),
trim($server->comment)
];
Storage::disk('export')->append($filename, implode(";", $infos));
}
}
/**
* Export projects as filename.csv
*
* @param string $filename
* @return void
*/
private function exportProjects(string $filename)
{
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Project> */
$projects = Project::orderBy('name', 'ASC')->get();
$fields = [
'DEV',
'name',
'dev_haproxy_id',
'dev_url',
'https',
'php',
'dev_web_server_id',
'dev_unix_user',
'dev_unix_pwd',
'dev_document_root',
'dev_db_server_id',
'dev_db_schema',
'dev_db_user',
'dev_db_pwd',
'dev_htaccess_user',
'dev_htaccess_pwd',
'dev_comment',
'dev_eai',
'dev_whitelist_fw',
];
Storage::disk('export')->append(
$filename,
// phpcs:ignore
"ENV;NOM;ID HAPROXY;URL;HTTPS;PHP;SERVEUR WEB;SSH;USER UNIX;PWD UNIX;DOCUMENT ROOT;SERVEUR BDD;SSH;USER DB;PWD DB;HTACCESS USER;HTACCESS PWD;COMMENTAIRE;FLUX EAI;WHITELIST FW"
);
$this->writeProjectExport($projects, $fields, $filename);
$prod = implode(";", $fields);
$prod = str_replace("dev", "prod", $prod);
$prod = str_replace("DEV", "PROD", $prod);
$prod = str_replace("prod_htaccess_user", "BLANK", $prod);
$prod = str_replace("prod_htaccess_pwd", "BLANK", $prod);
$fields = explode(";", $prod);
$this->writeProjectExport($projects, $fields, $filename);
}
/**
* Append Project to filename.csv
*
* @param Collection $projects
* @param string[] $fields
* @param string $filename
* @return void
*/
private function writeProjectExport(Collection $projects, array $fields, string $filename)
{
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Project> $projects */
foreach ($projects as $project) {
$infos = [];
foreach ($fields as $field) {
switch ($field) {
case 'DEV':
case 'PROD':
$infos[] = $field;
break;
case 'https':
$infos[] = $project->{$field} === 1 ? 'OUI' : 'NON';
break;
case 'dev_web_server_id':
case 'dev_db_server_id':
case 'prod_web_server_id':
case 'prod_db_server_id':
$server = Server::find($project->{$field});
$infos[] = trim($server->name);
$infos[] = trim($server->ssh);
break;
case 'BLANK':
$infos[] = "";
break;
default:
$infos[] = trim($project->{$field});
}//end switch
}//end foreach
Storage::disk('export')->append($filename, implode(";", $infos));
}//end foreach
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class TestMail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'uwil:mail-test';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send test email to equipe.web@ldc.fr';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
Mail::raw(sprintf('UWIL mail-test from %s %s', \config('app.name'), \config('app.url')), function ($msg) {
$msg->to('equipe.web@ldc.fr')->subject('Test Email');
});
return 0;
}
}

623
app/Enums/Country.php Normal file
View file

@ -0,0 +1,623 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum Country: int
{
use BaseEnum;
case afghanistan = 1;
case alandIslands = 2;
case albania = 3;
case algeria = 4;
case americanSamoa = 5;
case andorra = 6;
case angola = 7;
case anguilla = 8;
case antarctica = 9;
case antiguaAndBarbuda = 10;
case argentina = 11;
case armenia = 12;
case aruba = 13;
case australia = 14;
case austria = 15;
case azerbaijan = 16;
case bahrain = 17;
case bahamas = 18;
case bangladesh = 19;
case barbados = 20;
case belarus = 21;
case belgium = 22;
case belize = 23;
case benin = 24;
case bermuda = 25;
case bhutan = 26;
case boliviaPlurinationalStateOf = 27;
case bonaireSintEustatiusAndSaba = 28;
case bosniaAndHerzegovina = 29;
case botswana = 30;
case bouvetIsland = 31;
case brazil = 32;
case britishIndianOceanTerritory = 33;
case bruneiDarussalam = 34;
case bulgaria = 35;
case burkinaFaso = 36;
case burundi = 37;
case cambodia = 38;
case cameroon = 39;
case canada = 40;
case capeVerde = 41;
case caymanIslands = 42;
case centralAfricanRepublic = 43;
case chad = 44;
case chile = 45;
case china = 46;
case christmasIsland = 47;
case cocosKeelingIslands = 48;
case colombia = 49;
case comoros = 50;
case congo = 51;
case congoTheDemocraticRepublicOfThe = 52;
case cookIslands = 53;
case costaRica = 54;
case coteDivoire = 55;
case croatia = 56;
case cuba = 57;
case curacao = 58;
case cyprus = 59;
case czechRepublic = 60;
case denmark = 61;
case djibouti = 62;
case dominica = 63;
case dominicanRepublic = 64;
case ecuador = 65;
case egypt = 66;
case elSalvador = 67;
case equatorialGuinea = 68;
case eritrea = 69;
case estonia = 70;
case ethiopia = 71;
case falklandIslandsMalvinas = 72;
case faroeIslands = 73;
case fiji = 74;
case finland = 75;
case france = 76;
case frenchGuiana = 77;
case frenchPolynesia = 78;
case frenchSouthernTerritories = 79;
case gabon = 80;
case gambia = 81;
case georgia = 82;
case germany = 83;
case ghana = 84;
case gibraltar = 85;
case greece = 86;
case greenland = 87;
case grenada = 88;
case guadeloupe = 89;
case guam = 90;
case guatemala = 91;
case guernsey = 92;
case guinea = 93;
case guineaBissau = 94;
case guyana = 95;
case haiti = 96;
case heardIslandAndMcdonaldIslands = 97;
case holySeeVaticanCityState = 98;
case honduras = 99;
case hongKong = 100;
case hungary = 101;
case iceland = 102;
case india = 103;
case indonesia = 104;
case iranIslamicRepublicOf = 105;
case iraq = 106;
case ireland = 107;
case isleOfMan = 108;
case israel = 109;
case italy = 110;
case jamaica = 111;
case japan = 112;
case jersey = 113;
case jordan = 114;
case kazakhstan = 115;
case kenya = 116;
case kiribati = 117;
case koreaDemocraticPeoplesRepublicOf = 118;
case koreaRepublicOf = 119;
case kuwait = 120;
case kyrgyzstan = 121;
case laoPeoplesDemocraticRepublic = 122;
case latvia = 123;
case lebanon = 124;
case lesotho = 125;
case liberia = 126;
case libya = 127;
case liechtenstein = 128;
case lithuania = 129;
case luxembourg = 130;
case macao = 131;
case macedoniaTheFormerYugoslavRepublicOf = 132;
case madagascar = 133;
case malawi = 134;
case malaysia = 135;
case maldives = 136;
case mali = 137;
case malta = 138;
case marshallIslands = 139;
case martinique = 140;
case mauritania = 141;
case mauritius = 142;
case mayotte = 143;
case mexico = 144;
case micronesiaFederatedStatesOf = 145;
case moldovaRepublicOf = 146;
case monaco = 147;
case mongolia = 148;
case montenegro = 149;
case montserrat = 150;
case morocco = 151;
case mozambique = 152;
case myanmar = 153;
case namibia = 154;
case nauru = 155;
case nepal = 156;
case netherlands = 157;
case newCaledonia = 158;
case newZealand = 159;
case nicaragua = 160;
case niger = 161;
case nigeria = 162;
case niue = 163;
case norfolkIsland = 164;
case northernMarianaIslands = 165;
case norway = 166;
case oman = 167;
case pakistan = 168;
case palau = 169;
case palestineStateOf = 170;
case panama = 171;
case papuaNewGuinea = 172;
case paraguay = 173;
case peru = 174;
case philippines = 175;
case pitcairn = 176;
case poland = 177;
case portugal = 178;
case puertoRico = 179;
case qatar = 180;
case reunion = 181;
case romania = 182;
case russianFederation = 183;
case rwanda = 184;
case saintBarthelemy = 185;
case saintHelenaAscensionAndTristanDaCunha = 186;
case saintKittsAndNevis = 187;
case saintLucia = 188;
case saintMartinFrenchPart = 189;
case saintPierreAndMiquelon = 190;
case saintVincentAndTheGrenadines = 191;
case samoa = 192;
case sanMarino = 193;
case saoTomeAndPrincipe = 194;
case saudiArabia = 195;
case senegal = 196;
case serbia = 197;
case seychelles = 198;
case sierraLeone = 199;
case singapore = 200;
case sintMaartenDutchPart = 201;
case slovakia = 202;
case slovenia = 203;
case solomonIslands = 204;
case somalia = 205;
case southAfrica = 206;
case southGeorgiaAndTheSouthSandwichIslands = 207;
case southSudan = 208;
case spain = 209;
case sriLanka = 210;
case sudan = 211;
case suriname = 212;
case svalbardAndJanMayen = 213;
case swaziland = 214;
case sweden = 215;
case switzerland = 216;
case syrianArabRepublic = 217;
case taiwanProvinceOfChina = 218;
case tajikistan = 219;
case tanzaniaUnitedRepublicOf = 220;
case thailand = 221;
case timorLeste = 222;
case togo = 223;
case tokelau = 224;
case tonga = 225;
case trinidadAndTobago = 226;
case tunisia = 227;
case turkey = 228;
case turkmenistan = 229;
case turksAndCaicosIslands = 230;
case tuvalu = 231;
case uganda = 232;
case ukraine = 233;
case unitedArabEmirates = 234;
case unitedKingdom = 235;
case unitedStates = 236;
case unitedStatesMinorOutlyingIslands = 237;
case uruguay = 238;
case uzbekistan = 239;
case vanuatu = 240;
case venezuelaBolivarianRepublicOf = 241;
case vietNam = 242;
case virginIslandsBritish = 243;
case virginIslandsUs = 244;
case wallisAndFutuna = 245;
case westernSahara = 246;
case yemen = 247;
case zambia = 248;
case zimbabwe = 249;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::afghanistan->name => 'Afghanistan',
self::alandIslands->name => 'Åland Islands',
self::albania->name => 'Albania',
self::algeria->name => 'Algeria',
self::americanSamoa->name => 'American Samoa',
self::andorra->name => 'Andorra',
self::angola->name => 'Angola',
self::anguilla->name => 'Anguilla',
self::antarctica->name => 'Antarctica',
self::antiguaAndBarbuda->name => 'Antigua and Barbuda',
self::argentina->name => 'Argentina',
self::armenia->name => 'Armenia',
self::aruba->name => 'Aruba',
self::australia->name => 'Australia',
self::austria->name => 'Austria',
self::azerbaijan->name => 'Azerbaijan',
self::bahrain->name => 'Bahrain',
self::bahamas->name => 'Bahamas',
self::bangladesh->name => 'Bangladesh',
self::barbados->name => 'Barbados',
self::belarus->name => 'Belarus',
self::belgium->name => 'Belgium',
self::belize->name => 'Belize',
self::benin->name => 'Benin',
self::bermuda->name => 'Bermuda',
self::bhutan->name => 'Bhutan',
self::boliviaPlurinationalStateOf->name => 'Bolivia, Plurinational State of',
self::bonaireSintEustatiusAndSaba->name => 'Bonaire, Sint Eustatius and Saba',
self::bosniaAndHerzegovina->name => 'Bosnia and Herzegovina',
self::botswana->name => 'Botswana',
self::bouvetIsland->name => 'Bouvet Island',
self::brazil->name => 'Brazil',
self::britishIndianOceanTerritory->name => 'British Indian Ocean Territory',
self::bruneiDarussalam->name => 'Brunei Darussalam',
self::bulgaria->name => 'Bulgaria',
self::burkinaFaso->name => 'Burkina Faso',
self::burundi->name => 'Burundi',
self::cambodia->name => 'Cambodia',
self::cameroon->name => 'Cameroon',
self::canada->name => 'Canada',
self::capeVerde->name => 'Cape Verde',
self::caymanIslands->name => 'Cayman Islands',
self::centralAfricanRepublic->name => 'Central African Republic',
self::chad->name => 'Chad',
self::chile->name => 'Chile',
self::china->name => 'China',
self::christmasIsland->name => 'Christmas Island',
self::cocosKeelingIslands->name => 'Cocos (Keeling) Islands',
self::colombia->name => 'Colombia',
self::comoros->name => 'Comoros',
self::congo->name => 'Congo',
self::congoTheDemocraticRepublicOfThe->name => 'Congo, the Democratic Republic of the',
self::cookIslands->name => 'Cook Islands',
self::costaRica->name => 'Costa Rica',
self::coteDivoire->name => 'Côte d\'Ivoire',
self::croatia->name => 'Croatia',
self::cuba->name => 'Cuba',
self::curacao->name => 'Curaçao',
self::cyprus->name => 'Cyprus',
self::czechRepublic->name => 'Czech Republic',
self::denmark->name => 'Denmark',
self::djibouti->name => 'Djibouti',
self::dominica->name => 'Dominica',
self::dominicanRepublic->name => 'Dominican Republic',
self::ecuador->name => 'Ecuador',
self::egypt->name => 'Egypt',
self::elSalvador->name => 'El Salvador',
self::equatorialGuinea->name => 'Equatorial Guinea',
self::eritrea->name => 'Eritrea',
self::estonia->name => 'Estonia',
self::ethiopia->name => 'Ethiopia',
self::falklandIslandsMalvinas->name => 'Falkland Islands (Malvinas)',
self::faroeIslands->name => 'Faroe Islands',
self::fiji->name => 'Fiji',
self::finland->name => 'Finland',
self::france->name => 'France',
self::frenchGuiana->name => 'French Guiana',
self::frenchPolynesia->name => 'French Polynesia',
self::frenchSouthernTerritories->name => 'French Southern Territories',
self::gabon->name => 'Gabon',
self::gambia->name => 'Gambia',
self::georgia->name => 'Georgia',
self::germany->name => 'Germany',
self::ghana->name => 'Ghana',
self::gibraltar->name => 'Gibraltar',
self::greece->name => 'Greece',
self::greenland->name => 'Greenland',
self::grenada->name => 'Grenada',
self::guadeloupe->name => 'Guadeloupe',
self::guam->name => 'Guam',
self::guatemala->name => 'Guatemala',
self::guernsey->name => 'Guernsey',
self::guinea->name => 'Guinea',
self::guineaBissau->name => 'GuineaBissau',
self::guyana->name => 'Guyana',
self::haiti->name => 'Haiti',
self::heardIslandAndMcdonaldIslands->name => 'Heard Island and McDonald Islands',
self::holySeeVaticanCityState->name => 'Holy See (Vatican City State)',
self::honduras->name => 'Honduras',
self::hongKong->name => 'Hong Kong',
self::hungary->name => 'Hungary',
self::iceland->name => 'Iceland',
self::india->name => 'India',
self::indonesia->name => 'Indonesia',
self::iranIslamicRepublicOf->name => 'Iran, Islamic Republic of',
self::iraq->name => 'Iraq',
self::ireland->name => 'Ireland',
self::isleOfMan->name => 'Isle of Man',
self::israel->name => 'Israel',
self::italy->name => 'Italy',
self::jamaica->name => 'Jamaica',
self::japan->name => 'Japan',
self::jersey->name => 'Jersey',
self::jordan->name => 'Jordan',
self::kazakhstan->name => 'Kazakhstan',
self::kenya->name => 'Kenya',
self::kiribati->name => 'Kiribati',
self::koreaDemocraticPeoplesRepublicOf->name => 'Korea, Democratic People\'s Republic of',
self::koreaRepublicOf->name => 'Korea, Republic of',
self::kuwait->name => 'Kuwait',
self::kyrgyzstan->name => 'Kyrgyzstan',
self::laoPeoplesDemocraticRepublic->name => 'Lao People\'s Democratic Republic',
self::latvia->name => 'Latvia',
self::lebanon->name => 'Lebanon',
self::lesotho->name => 'Lesotho',
self::liberia->name => 'Liberia',
self::libya->name => 'Libya',
self::liechtenstein->name => 'Liechtenstein',
self::lithuania->name => 'Lithuania',
self::luxembourg->name => 'Luxembourg',
self::macao->name => 'Macao',
self::macedoniaTheFormerYugoslavRepublicOf->name => 'Macedonia, the Former Yugoslav Republic of',
self::madagascar->name => 'Madagascar',
self::malawi->name => 'Malawi',
self::malaysia->name => 'Malaysia',
self::maldives->name => 'Maldives',
self::mali->name => 'Mali',
self::malta->name => 'Malta',
self::marshallIslands->name => 'Marshall Islands',
self::martinique->name => 'Martinique',
self::mauritania->name => 'Mauritania',
self::mauritius->name => 'Mauritius',
self::mayotte->name => 'Mayotte',
self::mexico->name => 'Mexico',
self::micronesiaFederatedStatesOf->name => 'Micronesia, Federated States of',
self::moldovaRepublicOf->name => 'Moldova, Republic of',
self::monaco->name => 'Monaco',
self::mongolia->name => 'Mongolia',
self::montenegro->name => 'Montenegro',
self::montserrat->name => 'Montserrat',
self::morocco->name => 'Morocco',
self::mozambique->name => 'Mozambique',
self::myanmar->name => 'Myanmar',
self::namibia->name => 'Namibia',
self::nauru->name => 'Nauru',
self::nepal->name => 'Nepal',
self::netherlands->name => 'Netherlands',
self::newCaledonia->name => 'New Caledonia',
self::newZealand->name => 'New Zealand',
self::nicaragua->name => 'Nicaragua',
self::niger->name => 'Niger',
self::nigeria->name => 'Nigeria',
self::niue->name => 'Niue',
self::norfolkIsland->name => 'Norfolk Island',
self::northernMarianaIslands->name => 'Northern Mariana Islands',
self::norway->name => 'Norway',
self::oman->name => 'Oman',
self::pakistan->name => 'Pakistan',
self::palau->name => 'Palau',
self::palestineStateOf->name => 'Palestine, State of',
self::panama->name => 'Panama',
self::papuaNewGuinea->name => 'Papua New Guinea',
self::paraguay->name => 'Paraguay',
self::peru->name => 'Peru',
self::philippines->name => 'Philippines',
self::pitcairn->name => 'Pitcairn',
self::poland->name => 'Poland',
self::portugal->name => 'Portugal',
self::puertoRico->name => 'Puerto Rico',
self::qatar->name => 'Qatar',
self::reunion->name => 'Réunion',
self::romania->name => 'Romania',
self::russianFederation->name => 'Russian Federation',
self::rwanda->name => 'Rwanda',
self::saintBarthelemy->name => 'Saint Barthélemy',
self::saintHelenaAscensionAndTristanDaCunha->name => 'Saint Helena, Ascension and Tristan da Cunha',
self::saintKittsAndNevis->name => 'Saint Kitts and Nevis',
self::saintLucia->name => 'Saint Lucia',
self::saintMartinFrenchPart->name => 'Saint Martin (French part)',
self::saintPierreAndMiquelon->name => 'Saint Pierre and Miquelon',
self::saintVincentAndTheGrenadines->name => 'Saint Vincent and the Grenadines',
self::samoa->name => 'Samoa',
self::sanMarino->name => 'San Marino',
self::saoTomeAndPrincipe->name => 'Sao Tome and Principe',
self::saudiArabia->name => 'Saudi Arabia',
self::senegal->name => 'Senegal',
self::serbia->name => 'Serbia',
self::seychelles->name => 'Seychelles',
self::sierraLeone->name => 'Sierra Leone',
self::singapore->name => 'Singapore',
self::sintMaartenDutchPart->name => 'Sint Maarten (Dutch part)',
self::slovakia->name => 'Slovakia',
self::slovenia->name => 'Slovenia',
self::solomonIslands->name => 'Solomon Islands',
self::somalia->name => 'Somalia',
self::southAfrica->name => 'South Africa',
self::southGeorgiaAndTheSouthSandwichIslands->name => 'South Georgia and the South Sandwich Islands',
self::southSudan->name => 'South Sudan',
self::spain->name => 'Spain',
self::sriLanka->name => 'Sri Lanka',
self::sudan->name => 'Sudan',
self::suriname->name => 'Suriname',
self::svalbardAndJanMayen->name => 'Svalbard and Jan Mayen',
self::swaziland->name => 'Swaziland',
self::sweden->name => 'Sweden',
self::switzerland->name => 'Switzerland',
self::syrianArabRepublic->name => 'Syrian Arab Republic',
self::taiwanProvinceOfChina->name => 'Taiwan, Province of China',
self::tajikistan->name => 'Tajikistan',
self::tanzaniaUnitedRepublicOf->name => 'Tanzania, United Republic of',
self::thailand->name => 'Thailand',
self::timorLeste->name => 'TimorLeste',
self::togo->name => 'Togo',
self::tokelau->name => 'Tokelau',
self::tonga->name => 'Tonga',
self::trinidadAndTobago->name => 'Trinidad and Tobago',
self::tunisia->name => 'Tunisia',
self::turkey->name => 'Turkey',
self::turkmenistan->name => 'Turkmenistan',
self::turksAndCaicosIslands->name => 'Turks and Caicos Islands',
self::tuvalu->name => 'Tuvalu',
self::uganda->name => 'Uganda',
self::ukraine->name => 'Ukraine',
self::unitedArabEmirates->name => 'United Arab Emirates',
self::unitedKingdom->name => 'United Kingdom',
self::unitedStates->name => 'United States',
self::unitedStatesMinorOutlyingIslands->name => 'United States Minor Outlying Islands',
self::uruguay->name => 'Uruguay',
self::uzbekistan->name => 'Uzbekistan',
self::vanuatu->name => 'Vanuatu',
self::venezuelaBolivarianRepublicOf->name => 'Venezuela, Bolivarian Republic of',
self::vietNam->name => 'Viet Nam',
self::virginIslandsBritish->name => 'Virgin Islands, British',
self::virginIslandsUs->name => 'Virgin Islands, U.S.',
self::wallisAndFutuna->name => 'Wallis and Futuna',
self::westernSahara->name => 'Western Sahara',
self::yemen->name => 'Yemen',
self::zambia->name => 'Zambia',
self::zimbabwe->name => 'Zimbabwe',
];
/**
* Get Valid Shipping Countries
*
* @return array
*/
public static function validShippingCountries(): array
{
return [
self::france,
/**
* // * DROM COM .
// self::guadeloupe,
// self::martinique,
// self::frenchGuiana,
// self::reunion,
// self::saintPierreAndMiquelon,
// self::mayotte,
// self::saintBarthelemy,
// self::saintMartinFrenchPart,
// self::wallisAndFutuna,
// self::frenchPolynesia,
// self::newCaledonia
*/
];
}
/**
* Get Valid Shipping Countries
*
* @return array
*/
public static function validBillingCountries(): array
{
return [
self::france,
// * DROM COM.
self::guadeloupe,
self::martinique,
self::frenchGuiana,
self::reunion,
self::saintPierreAndMiquelon,
self::mayotte,
self::saintBarthelemy,
self::saintMartinFrenchPart,
self::wallisAndFutuna,
self::frenchPolynesia,
self::newCaledonia
];
}
/**
* Get Alpha 2 ISO code of valid shipping countries
*
* @return array
*/
public static function getValidShippingCountriesIsoAplha2Codes(): array
{
return ['FR'];
}
/**
* Get Alpha 2 ISO code of valid billing countries
*
* @return array
*/
public static function getValidBillingCountriesIsoAplha2Codes(): array
{
return \array_values(self::ISOCODES);
}
/**
* Custom added ISOCODES definition
*/
private const ISOCODES = [
self::france->name => 'FR',
self::guadeloupe->name => 'GP',
self::martinique->name => 'MQ',
self::frenchGuiana->name => 'GF',
self::reunion->name => 'RE',
self::saintPierreAndMiquelon->name => 'PM',
self::mayotte->name => 'YT',
self::saintBarthelemy->name => 'BL',
self::saintMartinFrenchPart->name => 'MF',
self::wallisAndFutuna->name => 'WF',
self::frenchPolynesia->name => 'PF',
self::newCaledonia->name => 'NC',
];
/**
* Get ISOCODE
*
* @return string
*/
public function iso(): string
{
return self::ISOCODES[$this->name];
}
/**
* Get enum ISOCODES
*
* @return array<string>
*/
public static function toIsoCodes(): array
{
return self::ISOCODES;
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* SMTP Types
*
* @method static self DEV()
* @method static self PROD()
*/
final class Environment extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'DEV' => 0,
'PROD' => 1,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'DEV' => 'Developpement',
'PROD' => 'Production'
];
}
}

30
app/Enums/InputType.php Normal file
View file

@ -0,0 +1,30 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum InputType: int
{
use BaseEnum;
case boolean = 1;
case dropdownArray = 2;
case email = 3;
case image = 4;
case imageArray = 5;
case labelArray = 6;
case miniblocArray = 7;
case number = 8;
case selectEnum = 9;
case password = 10;
case string = 11;
case text = 12;
case url = 13;
/** Vimeo only */
case videoVimeo = 14;
/** Youtube only */
case videoYoutube = 15;
/** Youtube or Vimeo. */
case video = 16;
}

View file

@ -1,49 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Servers Different Types
*
* @method static self v5_6()
* @method static self v7_3()
* @method static self v7_4()
* @method static self v8()
* @method static self v8_1()
*/
final class PhpVersion extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'v5_6' => 0,
'v7_3' => 1,
'v7_4' => 2,
'v8' => 3,
'v8_1' => 4
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'v5_6' => 'PHP 5.6',
'v7_3' => 'PHP 7.3',
'v7_4' => 'PHP 7.4',
'v8' => 'PHP 8',
'v8_1' => 'PHP 8.1'
];
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Projects Different Types
*
* @method static self other() Autre
* @method static self wp() WP
* @method static self laravel() Laravel
* @method static self symfony() Symfony
*/
final class ProjectType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'other' => 1,
'wp' => 2,
'laravel' => 3,
'symfony' => 4,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'other' => 'Autre',
'wp' => 'WP',
'laravel' => 'Laravel',
'symfony' => 'Symfony',
];
}
}

View file

@ -1,43 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Servers Different Types
*
* @method static self DB()
* @method static self WEB()
* @method static self ELASTIC()
*/
final class ServerType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'DB' => 0,
'WEB' => 1,
'ELASTIC' => 2
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'DB' => trans('Base de données'),
'WEB' => trans('WEB'),
'ELASTIC' => trans('Eslatic Search')
];
}
}

View file

@ -1,43 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* SMTP Types
*
* @method static self ldc() LDC Relay
* @method static self mailjet() Mailjet Relay
* @method static self sendinblue() Sendinblue Relay
*/
final class SmtpEnum extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'ldc' => 1,
'mailjet' => 2,
'sendinblue' => 3,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'ldc' => 'LDC Relay',
'mailjet' => 'Mailjet',
'sendinblue' => 'Sendinblue',
];
}
}

View file

@ -2,20 +2,27 @@
namespace App\Exceptions;
use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [];
/**
* A list of the inputs that are never flashed for validation exceptions.
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
@ -33,6 +40,9 @@ class Handler extends ExceptionHandler
public function register()
{
$this->reportable(function (Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
}
}

View file

@ -3,12 +3,12 @@
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\LDCNetwork;
use App\Models\Network;
class LDCNetworksController extends Controller
class NetworksController extends Controller
{
/**
* Api LDC Networks
* Api Networks
*
* @return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory
*/
@ -16,11 +16,11 @@ class LDCNetworksController extends Controller
{
$ips = [];
$networks = LDCNetwork::all();
$networks = Network::all();
foreach ($networks as $network) {
$infos = explode('.', $network->first_ip);
$min = $infos[3];
$max = explode('.', $network->last_ip)[3];
$min = $infos[3];
$max = explode('.', $network->last_ip)[3];
for ($i = $min; $i <= $max; $i++) {
$ips[] = "$infos[0].$infos[1].$infos[2].$i";
}

View file

@ -1,27 +0,0 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\UpdateProjectStatusRequest;
use App\Models\ProjectStatusChecker;
class StatusCheckerController extends Controller
{
/**
* Undocumented function
*
* @param \App\Http\Requests\Api\UpdateProjectStatusRequest $request
* @return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory
*/
public function index(UpdateProjectStatusRequest $request)
{
/** @var \App\Models\ProjectStatusChecker */
$project_sc = ProjectStatusChecker::where('project_uuid', '=', $request->project_uuid)
->firstOrNew();
$project_sc->fill($request->validated());
$project_sc->last_call = \now();
return $project_sc->save() ? response('OK') : response('KO');
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:backend')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard('backend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\Contracts\View\View
*/
public function showLoginForm(): View
{
return view('bo.auth.login');
}
/**
* Where to redirect users after login.
*
* @return string
*/
protected function redirectTo(): string
{
return response()->redirectToIntended()->getTargetUrl();
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Auth;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard('backend');
}
/**
* Where to redirect users after login.
*
* @return string
*/
protected function redirectTo(): string
{
return \route('bo.login');
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
class DashboardController extends Controller
{
/**
* Dashboard Page
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): View
{
return view('bo.pages.dashboard');
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Networks\StoreNetwork;
use App\Http\Requests\Bo\Networks\UpdateNetwork;
use App\Http\Requests\ValidateOtpRequest;
use App\Models\Network;
class NetworkController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$networks = Network::paginate(20);
return view('bo.pages.networks.index', compact('networks'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.pages.networks.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Networks\StoreNetwork $request
* @return \Illuminate\Http\Response
*/
public function store(StoreNetwork $request)
{
$network = new Network();
$network->fill($request->validated());
if ($network->saveOrFail()) {
return redirect()->route('bo.networks.edit', compact('network'))
->with('success', trans('Network créé.'));
}
return \back();
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Network $network
* @return \Illuminate\Http\Response
*/
public function edit(Network $network)
{
return view('bo.pages.networks.edit', ['network' => $network]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Networks\UpdateNetwork $request
* @param \App\Models\Network $network
* @return \Illuminate\Http\Response
*/
public function update(UpdateNetwork $request, Network $network)
{
$network->fill($request->validated());
if ($network->saveOrFail()) {
return redirect()->route('bo.networks.edit', compact('network'))
->with('success', trans('Network mis à jour.'));
}
return \back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Network $network
* @return \Illuminate\Http\Response
*/
public function destroy(Network $network)
{
return view('bo.pages.networks.delete', ['network' => $network]);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\ValidateOtpRequest $request
* @param \App\Models\Network $network
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function destroySubmit(ValidateOtpRequest $request, Network $network)
{
// phpcs:enable
if ($network->deleteOrFail()) {
return redirect()->route('bo.networks.index')
->with('warning', 'Network supprimé !');
}
return back();
}
}

View file

@ -2,14 +2,122 @@
namespace App\Http\Controllers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
string ...$fields
): Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$this->searchOnFields($query, $word, $fields);
} //end foreach
// Search whole using whole sentence.
$this->searchOnFields($query, $search, $fields);
if ($searchQuery) {
$searchQuery($query);
}
});
} //end if
return $query;
}
/**
* Build query search on all fields
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param array $fields
* @return \Illuminate\Database\Eloquent\Builder
*/
private function searchOnFields(Builder $query, string $search, array $fields): Builder
{
$table = $query->getModel()->getTable();
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
} else {
$query->orWhereRaw(
"UPPER(`$table`.`$f`) LIKE UPPER(?)",
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
}
}
return $query;
}
/**
* Sort columns with a query
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function sortQuery(Builder $query, array $ignore = []): Builder
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
if (request()->sort_col) {
Session::put("$rName.sort_col", request()->sort_col);
Session::put("$rName.sorted", true);
}
if (request()->sort_way) {
Session::put("$rName.sort_way", request()->sort_way);
Session::put("$rName.sorted", true);
}
if (request()->rst) {
Session::remove("$rName.sorted");
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
}
return $query;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
class HomeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): View
{
return view('fo.pages.home');
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
class HomeController extends Controller
{
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
// $secret = Totp::GenerateSecret(16);
// Display new key to user so they can enter it in Google Authenticator or Authy
// echo strtoupper(Base32::encode($secret));exit;
// otpauth://totp/LDC%20WEB:mail@ldc.fr?secret=secret&issuer=LDC%20WEB
return view('home');
}
/**
* Generate QR code
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function qr()
{
$user = Auth::user();
$qrcode = QrCode::size(200)->generate(
"otpauth://totp/LDC%20WEB:$user->email?secret=$user->totp_secret&issuer=LDC%20WEB"
);
return view("qrcode", compact('qrcode'));
}
}

View file

@ -1,108 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\LDCNetworks\StoreLDCNetwork;
use App\Http\Requests\LDCNetworks\UpdateLDCNetwork;
use App\Http\Requests\ValidateOtpRequest;
use App\Models\LDCNetwork;
class LDCNetworkController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$ldc_networks = LDCNetwork::all();
return view('ldc-networks.index', compact('ldc_networks'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('ldc-networks.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\LDCNetworks\StoreLDCNetwork $request
* @return \Illuminate\Http\Response
*/
public function store(StoreLDCNetwork $request)
{
$ldc_network = new LDCNetwork();
$ldc_network->fill($request->validated());
if ($ldc_network->saveOrFail()) {
return redirect()->route('ldc-networks.edit', compact('ldc_network'))
->with('success', trans('LDC Network créé.'));
}
return \back();
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\LDCNetwork $ldc_network
* @return \Illuminate\Http\Response
*/
public function edit(LDCNetwork $ldc_network)
{
return view('ldc-networks.edit', ['ldc_network' => $ldc_network]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\LDCNetworks\UpdateLDCNetwork $request
* @param \App\Models\LDCNetwork $ldc_network
* @return \Illuminate\Http\Response
*/
public function update(UpdateLDCNetwork $request, LDCNetwork $ldc_network)
{
$ldc_network->fill($request->validated());
if ($ldc_network->saveOrFail()) {
return redirect()->route('ldc-networks.edit', compact('ldc_network'))
->with('success', trans('LDC Network mis à jour.'));
}
return \back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\LDCNetwork $ldc_network
* @return \Illuminate\Http\Response
*/
public function destroy(LDCNetwork $ldc_network)
{
return view('ldc-networks.delete', ['ldc_network' => $ldc_network]);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\ValidateOtpRequest $request
* @param \App\Models\LDCNetwork $ldc_network
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function destroySubmit(ValidateOtpRequest $request, LDCNetwork $ldc_network)
{
// phpcs:enable
if ($ldc_network->deleteOrFail()) {
return redirect()->route('ldc-networks.index')
->with('warning', 'LDC Network supprimé !');
}
return back();
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ValidateOtpRequest;
use App\Providers\RouteServiceProvider;
use GuzzleHttp\Exception\RequestException;
use lfkeitel\phptotp\Base32;
use lfkeitel\phptotp\Totp;
use Illuminate\Support\Facades\Auth;
class OTPController extends Controller
{
/**
* Show verifiy otp page
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function verify()
{
$user = session()->get('tmpUserOTP');
if (!$user) {
return redirect(RouteServiceProvider::HOME);
}
return view('auth.verify');
}
/**
* Check OPT request
*
* @param ValidateOtpRequest $request
* @return \Illuminate\Http\RedirectResponse
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function check(ValidateOtpRequest $request)
{
//phpcs:enable
Auth::login(session()->get('tmpUserOTP'));
return \response()->redirectTo(RouteServiceProvider::HOME);
}
/**
* Send OPT by SMS
*
* @return \Illuminate\Http\RedirectResponse
*/
public function sendOtpBySms()
{
$user = session()->get('tmpUserOTP');
if (!$user) {
return back()->withErrors('User not found.');
}
if (is_null($user->phone)) {
return back()->withErrors('Phone number missing.');
}
$otp = (new Totp())->GenerateToken(Base32::decode($user->totp_secret));
if (!$otp) {
return back()->withErrors('Erreur lors de la génération du code OTP');
}
/** @var \App\Lib\LDC\MailjetSms */
$mj = app('MailjetSms');
try {
$mjResponse = $mj->send("OTP : " . implode(' ', str_split($otp)), $user->phone);
/** @var \GuzzleHttp\Psr7\Response $mjResponse */
$responseStatus = $mjResponse->getStatusCode();
if ($responseStatus == 200) {
return back()->with('success', 'SMS envoyé.');
}
return back()->withErrors('SMS not sent.');
} catch (RequestException $e) {
report($e);
return back()->withErrors('Erreur lors de l\'envoi du SMS');
}
}
}

View file

@ -1,161 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Enums\Environment;
use App\Enums\ServerType;
use App\Http\Requests\Projects\StoreProjectRequest;
use App\Http\Requests\Projects\UpdateProjectRequest;
use App\Http\Requests\ValidateOtpRequest;
use App\Models\Project;
use App\Models\Server;
use Ramsey\Uuid\Uuid;
class ProjectController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$projects = Project::orderBy('name')->get();
return view('projects.index', ['projects' => $projects]);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$dev_db_server = Server::where([
['env', Environment::DEV()],
['type', ServerType::DB()]
])->get();
$dev_web_server = Server::where([
['env', Environment::DEV()],
['type', ServerType::WEB()]
])->get();
$prod_db_server = Server::where([
['env', Environment::PROD()],
['type', ServerType::DB()]
])->get();
$prod_web_server = Server::where([
['env', Environment::PROD()],
['type', ServerType::WEB()]
])->get();
return view('projects.create', \compact(
'dev_db_server',
'dev_web_server',
'prod_db_server',
'prod_web_server'
));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Projects\StoreProjectRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreProjectRequest $request)
{
$project = new Project();
$project->uuid = Uuid::uuid4();
$project->fill($request->validated());
$project->dev_login = \json_decode($project->dev_login);
$project->prod_login = \json_decode($project->prod_login);
if ($project->saveOrFail()) {
return redirect()->route('projects.edit', compact('project'))
->with('success', trans('Projet ajouté.'));
}
return \back();
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Project $project
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(Project $project)
{
// phpcs:enable
$dev_db_server = Server::where([
['env', Environment::DEV()],
['type', ServerType::DB()]
])->get();
$dev_web_server = Server::where([
['env', Environment::DEV()],
['type', ServerType::WEB()]
])->get();
$prod_db_server = Server::where([
['env', Environment::PROD()],
['type', ServerType::DB()]
])->get();
$prod_web_server = Server::where([
['env', Environment::PROD()],
['type', ServerType::WEB()]
])->get();
return view('projects.edit', \compact(
'project',
'dev_db_server',
'dev_web_server',
'prod_db_server',
'prod_web_server'
));
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Projects\UpdateProjectRequest $request
* @param \App\Models\Project $project
* @return \Illuminate\Http\Response
*/
public function update(UpdateProjectRequest $request, Project $project)
{
$project->fill($request->validated());
$project->dev_login = \json_decode($project->dev_login);
$project->prod_login = \json_decode($project->prod_login);
if ($project->saveOrFail()) {
return redirect()->route('projects.edit', compact('project'))
->with('success', trans('Projet mis à jour.'));
}
return \back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Project $project
* @return \Illuminate\Http\Response
*/
public function destroy(Project $project)
{
return view('projects.delete', ['project' => $project]);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\ValidateOtpRequest $request
* @param \App\Models\Project $project
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function destroySubmit(ValidateOtpRequest $request, Project $project)
{
// phpcs:enable
if ($project->deleteOrFail()) {
return redirect()->route('projects.index')
->with('warning', 'Projet supprimé !');
}
return back();
}
}

View file

@ -1,120 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Servers\StoreServerRequest;
use App\Http\Requests\Servers\UpdateServerRequest;
use App\Http\Requests\ValidateOtpRequest;
use App\Models\Project;
use App\Models\Server;
class ServerController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$servers = Server::all();
return view('servers.index', compact('servers'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('servers.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Servers\StoreServerRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreServerRequest $request)
{
$server = new Server();
$server->fill($request->validated());
if ($server->saveOrFail()) {
return redirect()->route('servers.edit', compact('server'))
->with('success', trans('Server créé.'));
}
return \back();
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Server $server
* @return \Illuminate\Http\Response
*/
public function edit(Server $server)
{
return view('servers.edit', ['server' => $server]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Servers\UpdateServerRequest $request
* @param \App\Models\Server $server
* @return \Illuminate\Http\Response
*/
public function update(UpdateServerRequest $request, Server $server)
{
$server->fill($request->validated());
if ($server->saveOrFail()) {
return redirect()->route('servers.edit', compact('server'))
->with('success', trans('Server mis à jour.'));
}
return \back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Server $server
* @return \Illuminate\Http\Response
*/
public function destroy(Server $server)
{
$isUsed = false;
if ($server->type === 'DB' || $server->type === 'WEB') {
$projects = Project::where('dev_db_server_id', '=', $server->id)
->orWhere('dev_web_server_id', '=', $server->id)
->orWhere('prod_db_server_id', '=', $server->id)
->orWhere('prod_web_server_id', '=', $server->id)
->get();
$isUsed = $projects->count() ? $projects : false;
}
return view('servers.delete', ['server' => $server, 'isUsed' => $isUsed]);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\ValidateOtpRequest $request
* @param \App\Models\Server $server
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function destroySubmit(ValidateOtpRequest $request, Server $server)
{
// phpcs:enable
if ($server->deleteOrFail()) {
return redirect()->route('servers.index')
->with('warning', 'Server supprimé !');
}
return back();
}
}

View file

@ -1,110 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ServiceAccounts\StoreServiceAccountsRequest;
use App\Http\Requests\ServiceAccounts\UpdateServiceAccountsRequest;
use App\Http\Requests\ValidateOtpRequest;
use App\Models\ServiceAccount;
class ServiceAccountsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$service_accounts = ServiceAccount::all()->sortBy('name');
return view('service-accounts.index', compact('service_accounts'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('service-accounts.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\ServiceAccounts\StoreServiceAccountsRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreServiceAccountsRequest $request)
{
$service_account = new ServiceAccount();
$service_account->fill($request->validated());
if ($service_account->saveOrFail()) {
return redirect()->route('service-accounts.edit', compact('service_account'))
->with('success', trans('Compte de service créé.'));
}
return \back();
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\ServiceAccount $service_account
* @return \Illuminate\Http\Response
*/
public function edit(ServiceAccount $service_account)
{
return view('service-accounts.edit', [
'service_account' => $service_account
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\ServiceAccounts\UpdateServiceAccountsRequest $request
* @param \App\Models\ServiceAccount $service_account
* @return \Illuminate\Http\Response
*/
public function update(UpdateServiceAccountsRequest $request, ServiceAccount $service_account)
{
$service_account->fill($request->validated());
if ($service_account->saveOrFail()) {
return redirect()->route('service-accounts.edit', compact('service_account'))
->with('success', trans('Compte de service mis à jour.'));
}
return \back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\ServiceAccount $service_account
* @return \Illuminate\Http\Response
*/
public function destroy(ServiceAccount $service_account)
{
return view('service-accounts.delete', ['service_account' => $service_account]);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Http\Requests\ValidateOtpRequest $request
* @param \App\Models\ServiceAccount $service_account
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function destroySubmit(ValidateOtpRequest $request, ServiceAccount $service_account)
{
// phpcs:enable
if ($service_account->deleteOrFail()) {
return redirect()->route('service-accounts.index')
->with('warning', 'Compte de service supprimé !');
}
return back();
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Project;
use Illuminate\Support\Facades\Http;
class StatusCheckerController extends Controller
{
/**
* Fetch Status
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
$wpReleases = Http::get(config('status-checker.wp_endpoint'))->json();
$laravelReleases = Http::withOptions([
'allow_redirects' => false,
])->asJson()->get(config('status-checker.laravel_endpoint'))->json();
$wp_latest = collect(collect(\collect($wpReleases)->get('offers'))->first())->get('version');
$laravel_latest = ltrim(collect(collect($laravelReleases)->first())->get('tag_name'), 'v');
$projects = Project::orderBy('name')->get();
return view('status-checker.index', compact(
'projects',
'wp_latest',
'laravel_latest'
));
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use App\Http\Requests\Webhooks\GitlabRequest;
use App\Mail\Webhooks\Gitlab;
use Illuminate\Support\Facades\Mail;
class GitlabController extends Controller
{
/**
* Handle gitlab request
*
* @param GitlabRequest $request
* @return void
*/
public function handle(GitlabRequest $request)
{
// TODO: Check all request inputs, before sending unvalidated data by mail.
$event_type = $request->event_type;
if ($event_type === 'merge_request') {
// Si le state est <> de opened on ne fait rien .
if ($request->object_attributes['state'] !== 'opened') {
exit;
}
// Si l'action est set et egale à approved on ne fait rien .
if (isset($request->object_attributes['action']) && $request->object_attributes['action'] === 'approved') {
exit;
}
$datas = [];
$datas['user'] = $request->user['name'];
$datas['project_name'] = $request->project['name'];
$datas['project_path'] = $request->project['path_with_namespace'];
$datas['attributes_description'] = $request->object_attributes['description'];
$datas['attributes_status'] = $request->object_attributes['merge_status'];
$datas['attributes_request_status'] = $request->object_attributes['state'];
$datas['attributes_source_branch'] = $request->object_attributes['source_branch'];
$datas['attributes_target_branch'] = $request->object_attributes['target_branch'];
$datas['attributes_url'] = $request->object_attributes['url'];
$datas['last_commit_author'] = $request->object_attributes['last_commit']['author']['name'];
Mail::to(\explode(',', config('webhooks.gitlab.notify')))->send(new Gitlab($datas));
}//end if
}
}

View file

@ -16,7 +16,7 @@ class Kernel extends HttpKernel
protected $middleware = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
@ -34,7 +34,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
@ -42,15 +41,9 @@ class Kernel extends HttpKernel
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
];
/**
@ -60,36 +53,18 @@ class Kernel extends HttpKernel
*
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'apiCheck' => \App\Http\Middleware\ApiCheckCredentialMiddleware::class,
'ldcnetwork' => \App\Http\Middleware\LDCNetwork::class
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
'apiCheck' => \App\Http\Middleware\ApiCheckCredentialMiddleware::class
];
}

View file

@ -11,9 +11,9 @@ class ApiCheckCredentialMiddleware
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
public function handle(mixed $request, \Closure $next)
{
if ($request->token !== \config('api.key')) {
if ($request->header('Authorization') !== \config('api.key')) {
return response('Unauthorized', 401);
}

View file

@ -3,22 +3,38 @@
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Support\Facades\URL;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param mixed $request
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
protected function redirectTo(Request $request): ?string
{
/** @var \Illuminate\Http\Request $request */
$logins = [
'backend' => route('bo.login')
];
if (!$request->expectsJson()) {
session(['redirectURL' => URL::full()]);
return route('saml2_login', 'ldc');
// * Notify must login
Session::flash('warning', trans('Veuillez vous connecter pour continuer'));
foreach ($request->route()->gatherMiddleware() as $middleware) {
if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) {
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
if ($route) {
return $route;
}
}
}
return route('login');
}
abort(401);
return null;
}
}

View file

@ -9,8 +9,7 @@ class EncryptCookies extends Middleware
/**
* The names of the cookies that should not be encrypted.
*
* @var array
* @var array<int, string>
*/
protected $except = [
];
protected $except = [];
}

View file

@ -2,8 +2,9 @@
namespace App\Http\Middleware;
use App\Lib\ToolboxHelper;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class HtmlSanitizer
{
@ -12,21 +13,34 @@ class HtmlSanitizer
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param mixed ...$params
* @return mixed
* @param string|array ...$params
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, \Closure $next, ...$params)
public function handle(Request $request, \Closure $next, string|array ...$params): Response
{
$inputs = $request->all();
array_walk_recursive($inputs, function (&$input, $name) use ($params) {
/** @var callable|null */
$cleanInput = null;
$cleanInput = function (
null|string|array &$input,
string $name,
?string $rootName = null
) use (
$params,
&$cleanInput
): void {
// * Nested element array
if (is_array($input)) {
array_walk_recursive($input, $cleanInput, $name);
}
if (!is_string($input)) {
return;
}
if (in_array($name, $params)) {
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
// ? Is a json input ?
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
// ! Decode each json field recursive.
\array_walk_recursive($isJson, function (&$item) {
\array_walk_recursive($isJson, function (string|array &$item) {
if (\is_string($item)) {
$item = clean($item);
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
@ -39,7 +53,8 @@ class HtmlSanitizer
} else {
$input = strip_tags($input);
}
});
};
\array_walk($inputs, $cleanInput);
$request->merge($inputs);
return $next($request);
}

View file

@ -1,53 +0,0 @@
<?php
namespace App\Http\Middleware;
use App\Models\LDCNetwork as ModelsLDCNetwork;
use Closure;
use Illuminate\Support\Facades\Cache;
class LDCNetwork
{
/**
* Handle an incoming request.
*
* @param mixed $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (\config('app.env') === 'local' || (\config('app.env') === 'production' && \config('app.debug') === true)) {
return $next($request);
}
/** HA PROXY HACK */
$ip = request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
$restrictedIps = Cache::get('ldcweb_ip_ranges');
if (\is_null($restrictedIps)) {
$networks = ModelsLDCNetwork::all();
$networks = $networks->map(function (ModelsLDCNetwork $network) {
$infos = collect(explode('.', strval($network->first_ip)));
$min = intval($infos->get(3));
$max = collect(explode('.', strval($network->last_ip)))->get(3);
for ($i = $min; $i <= $max; $i++) {
$restrictedIps[] = $infos->get(0) . '.' .
$infos->get(1) . '.' .
$infos->get(2) . ".$i";
}
return $restrictedIps;
});
$restrictedIps = [];
foreach ($networks->toArray() as $network) {
$restrictedIps = array_merge($restrictedIps, $network);
}
Cache::put('ldcweb_ip_ranges', $restrictedIps, now()->addHours(4));
}
if (!is_null($restrictedIps) && in_array($ip, $restrictedIps)) {
return $next($request);
}
abort(403);
}
}

View file

@ -2,30 +2,41 @@
namespace App\Http\Middleware;
use Aacotroneo\Saml2\Saml2Auth;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string|null ...$guards
* @return \Symfony\Component\HttpFoundation\Response
* @throws \RuntimeException If guard is unhandled.
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next)
public function handle(Request $request, \Closure $next, ...$guards): Response
{
if ($request->ajax()) {
// Or, return a response that causes client side js to redirect to '/routesPrefix/myIdp1/login' .
return response('Unauthorized.', 401);
} else {
$saml2Auth = new Saml2Auth(Saml2Auth::loadOneLoginAuthFromIpdConfig('ldc'));
session(['redirectURL' => URL::full()]);
return $saml2Auth->login(RouteServiceProvider::HOME);
// phpcs:enable phpcs:disable
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
switch ($guard) {
case 'frontend':
return \redirect()->route('home');
case 'backend':
if (!$request->routeIs('bo.*')) {
continue;
}
return \redirect()->route('bo.dashboard');
default:
throw new \RuntimeException("Unhandled guard `{$guard}` .");
}
}
}
return $next($request);

View file

@ -6,6 +6,8 @@ use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
@ -24,18 +26,20 @@ class SecureHeaders
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next)
public function handle(Request $request, Closure $next): Response
{
View::share('nonce', $nonce = \uniqid());
// phpcs:enable
View::share('nonce', $nonce = Vite::useCspNonce());
// * For VueJS dev tool to execute
$cssDev = config('app.debug') ? "'unsafe-inline'" : "'nonce-$nonce'";
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-$nonce'";
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ?
\sprintf('report-uri %s', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList);
$response = $next($request);
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
@ -45,19 +49,37 @@ class SecureHeaders
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
$cspPolicies = [
"default-src 'none';",
"manifest-src 'self';",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob:;",
"img-src * data: blob:;",
"frame-src https://www.youtube.com;",
'frame-src ' . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/'
]) . ';',
"font-src 'self' data: https://fonts.gstatic.com;",
// ! Using CKEditor wiill must have unsafe-inline style
// ! Using CKEditor will must have unsafe-inline style
// phpcs:ignore Generic.Files.LineLength.TooLong
// "style-src 'self' 'unsafe-inline';",
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
'https://cdnjs.cloudflare.com',
// * File Manager
'https://fonts.googleapis.com',
'https://cdn.jsdelivr.net',
'https://use.fontawesome.com',
'https://fonts.googleapis.com'
]) . ';',
// ! If you dont use ckeditor use this
"style-src 'self' $cssDev https://fonts.googleapis.com;",
// "style-src 'self' 'nonce-$nonce';",
// phpcs:ignore Generic.Files.LineLength.TooLong
"script-src 'self' $jsDev;"
"script-src 'self' {$jsDev} " . \implode(' ', [
'https: blob:',
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
"$scheme://www.google-analytics.com",
]) . ';'
];
$response->headers->set(
'Content-Security-Policy',
implode(' ', $cspPolicies) . $reportUri

View file

@ -9,9 +9,10 @@ class TrimStrings extends Middleware
/**
* The names of the attributes that should not be trimmed.
*
* @var array
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];

View file

@ -2,7 +2,7 @@
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
@ -10,7 +10,7 @@ class TrustProxies extends Middleware
/**
* The trusted proxies for this application.
*
* @var array|string
* @var array<int, string>|string|null
*/
protected $proxies;
@ -19,5 +19,10 @@ class TrustProxies extends Middleware
*
* @var integer
*/
protected $headers = Request::HEADER_X_FORWARDED_FOR;
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [];
}

View file

@ -6,19 +6,12 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var boolean
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
* @var array<int, string>
*/
protected $except = [
'webhooks/*'
'/csp-report'
];
}

View file

@ -1,51 +0,0 @@
<?php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
class UpdateProjectStatusRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return ($this->token === \config('api.key')) && !\is_null($this->datas);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$data = \json_decode($this->datas, true);
$this->merge([
'project_uuid' => collect($data)->get('uuid'),
'sitemap' => collect($data)->get('sitemap') === 'true' ? true : false,
'robots' => collect($data)->get('robots') === 'true' ? true : false,
'code' => collect($data)->get('code'),
'logs' => collect($data)->get('logs')
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'project_uuid' => 'required|exists:projects,uuid',
'sitemap' => 'required|boolean',
'robots' => 'required|boolean',
'code' => 'required|array',
'logs' => 'required|numeric',
];
}
}

View file

View file

@ -1,13 +1,13 @@
<?php
namespace App\Http\Requests\LDCNetworks;
namespace App\Http\Requests\Bo\Networks;
use App\Rules\CidrRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class StoreLDCNetwork extends FormRequest
class StoreNetwork extends FormRequest
{
/**
* Determine if the user is authorized to make this request.

View file

@ -0,0 +1,7 @@
<?php
namespace App\Http\Requests\Bo\Networks;
class UpdateNetwork extends StoreNetwork
{
}

View file

@ -0,0 +1,229 @@
<?php
namespace App\Http\Requests\Bo;
use App\Enums\InputType;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
abstract class UpdateMiniCrudRequest extends FormRequest
{
/**
* The model parameter name
*
* @var string
*/
protected $modelName = null;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
public function rules()
{
// phpcs:enable
$rules = [];
foreach ($this->{$this->modelName}->data as $iptName => $input) {
$input = \json_decode(\json_encode($input), true);
switch ($input['inputType']) {
case InputType::number->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('required|numeric', $input)
]);
break;
case InputType::videoYoutube->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('regex:%([^"&?\/\s]{11})%i', $input)
]);
break;
case InputType::videoVimeo->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('regex:%([0-9]{8,10})%i', $input)
]);
break;
case InputType::video->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate([
'regex:%([0-9]{8,10}|[^"&?\/\s]{11})%i'
], $input)
]);
break;
case InputType::string->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('required|string|min:3', $input)
]);
break;
case InputType::text->value:
$mergeRules = $this->compileValidate('required|string|min:15', $input);
$mergeRules = collect($mergeRules)
->map(function ($rule) {
if (\strpos($rule, 'max:') !== false) {
$max = \intval(str_replace('max:', '', $rule));
return function ($attribute, $value, $fail) use ($max) {
if (\strlen(\strip_tags($value)) > $max) {
$fail("$attribute ne doit pas excéder $max caractères");
}
};
}
return $rule;
});
$rules = array_merge($rules, [
"data.$iptName.value" => $mergeRules
]);
break;
case InputType::image->value:
$required = $this->isUpdate() ? 'sometimes' : 'required';
$ipt = $this->data[$iptName];
$max = isset($ipt['dimensionsMax']);
$dim = \is_string($input['dimensions']) ? collect(explode(
',',
(isset($ipt['dimensionsMax']) and is_string($ipt['dimensionsMax'])) ?
$ipt['dimensionsMax'] : $input['dimensions']
))->mapWithKeys(function ($v) {
$v = explode(' ', $v);
return [$v[0] => $v[1]];
}) : $input['dimensions'];
// * Adapt dimensions
$sizes = (object)[
'minHeight' => $dim['height'],
'maxHeight' => $dim['height'],
'minWith' => $dim['width'],
'maxWith' => $dim['width'],
];
$rules = array_merge($rules, [
"data.$iptName.value" => [
function (string $attribute, $value, callable $fail) use ($sizes) {
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
}
],
$this->compileValidate(
implode('|', [
$required,
'image',
'mimes:png,jpg,jpeg',
sprintf(
'dimensions:%s=%d,%s=%d',
$max ? 'max_height' : 'height',
$dim['height'],
$max ? 'max_width' : 'width',
$dim['width']
)
]),
$input
),
"data.$iptName.alt" => $this->compileValidate("$required|string|min:5", $input),
"data.$iptName.title" => $this->compileValidate("$required|string|min:5", $input)
]);
break;
case InputType::url->value:
$rules = array_merge($rules, [
"data.$iptName.value" => [
'required', 'url',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
try {
ToolboxHelper::assertNoInternalUrls($attribute, $value);
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
} catch (ValidationException $e) {
$fail(collect($e->errors())->first()[0]);
}
}
]
]);
break;
case InputType::boolean->value:
$all = $this->all();
$nested = ToolboxHelper::dotStringToNestedArray(
"data.$iptName.value",
$this->input("data.$iptName.value") ? true : false
);
$all = ToolboxHelper::arrayMergeRecursiveDistinct($all, $nested);
$this->merge($all);
$rules = array_merge($rules, [
"data.$iptName.value" => 'boolean'
]);
break;
} //end switch
} //end foreach
return $rules;
}
/**
* Undocumented function
*
* @param string|array $inputTypeValidate
* @param array $input
* @return array
*/
public function compileValidate(string|array $inputTypeValidate, array $input): array
{
if (\is_string($inputTypeValidate)) {
$inputTypeValidate = explode('|', $inputTypeValidate);
}
/** @var array|string */
$inputValidate = \collect($input)->get('validate', $input);
$inputValidate = \is_string($inputValidate) ? explode('|', $inputValidate) : $inputValidate;
foreach ($inputValidate as $validate) {
if (!\in_array($validate, $inputTypeValidate)) {
$inputTypeValidate[] = $validate;
}
}
$test = \collect($inputTypeValidate)->all();
$test = \collect($inputTypeValidate)->sort(function ($valueA, $valueB) {
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
return $valueA - $valueB;
})->unique()->all();
return \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) {
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
return $valueA - $valueB;
})->all();
}
/**
* Does the rulle need to come first ?
*
* @param string $rule
* @return boolean
*/
private function ruleHasToComeFirst(string $rule): bool
{
$rule = \collect(\explode(':', $rule, 1))->filter()->first(null, '');
return \collect([
'nullable', 'present', 'required', 'required_if',
'required_unless', 'required_with', 'required_with_all',
'required_without', 'required_without_all', 'required_with_all',
'missing', 'missing_if', 'missing_unless',
'missing_with', 'missing_with_all',
])->first(fn (string $firstRule) => \strpos($rule, $firstRule) === 0) ? true : false;
}
/**
* Is this request an update request ?
*
* @return boolean
*/
private function isUpdate(): bool
{
return $this->isMethod('PUT');
}
}

View file

View file

@ -1,7 +0,0 @@
<?php
namespace App\Http\Requests\LDCNetworks;
class UpdateLDCNetwork extends StoreLDCNetwork
{
}

View file

@ -1,112 +0,0 @@
<?php
namespace App\Http\Requests\Projects;
use App\Enums\PhpVersion;
use App\Enums\ProjectType;
use App\Enums\SmtpEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
class StoreProjectRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return Auth::check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'https' => $this->https ? true : false
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [];
if (Auth::user()->equipe_web_member) {
$rules = [
'prod_login' => 'required|json',
'dev_login' => 'required|json',
'prod_bo_url' => 'required|url',
'dev_bo_url' => 'required|url',
];
}
return \array_merge($rules, [
'name' => 'required|string|min:2|max:255',
'display_name' => 'required|string|min:2|max:255',
'php' => ['required', new EnumIsValidRule(PhpVersion::class)],
'type' => ['required', new EnumIsValidRule(ProjectType::class)],
'https' => 'required|boolean',
'prod_haproxy_id' => 'nullable|regex:/^P?\d{3}$/',
'dev_haproxy_id' => 'nullable|regex:/^P?\d{3}$/',
'prod_url' => 'nullable|url|max:255',
'dev_url' => 'nullable|url|max:255',
'prod_document_root' => 'nullable|regex:/^(\/)?([^\/\0]+(\/)?)+$/|max:255',
'dev_document_root' => 'nullable|regex:/^(\/)?([^\/\0]+(\/)?)+$/|max:255',
'prod_unix_user' => 'nullable|string|max:255',
'prod_unix_pwd' => 'nullable|string|max:255',
'dev_unix_user' => 'nullable|string|max:255',
'dev_unix_pwd' => 'nullable|string|max:255',
'prod_db_user' => 'nullable|string|max:255',
'prod_db_pwd' => 'nullable|string|max:255',
'dev_db_user' => 'nullable|string|max:255',
'dev_db_pwd' => 'nullable|string|max:255',
'prod_db_schema' => 'nullable|string|max:255',
'dev_db_schema' => 'nullable|string|max:255',
'prod_db_server_id' => 'sometimes|exists:servers,id',
'dev_db_server_id' => 'sometimes|exists:servers,id',
'prod_web_server_id' => 'sometimes|exists:servers,id',
'dev_web_server_id' => 'sometimes|exists:servers,id',
'prod_comment' => 'nullable|string|max:255',
'dev_comment' => 'nullable|string|max:255',
'prod_eai' => 'nullable|string|max:255',
'dev_eai' => 'nullable|string|max:255',
'prod_whitelist_fw' => 'nullable|string|max:255',
'dev_whitelist_fw' => 'nullable|string|max:255',
'dev_htaccess_user' => 'required|string|min:2|max:255',
'dev_htaccess_pwd' => 'required|string|min:2|max:255',
'prod_ga_id' => 'nullable|string|max:50',
// phpcs:ignore Generic.Files.LineLength.TooLong
'prod_laravel_app_key' => 'nullable|regex:/^base64:(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/|max:255',
// phpcs:ignore Generic.Files.LineLength.TooLong
'dev_laravel_app_key' => 'nullable|regex:/^base64:(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/|max:255',
'prod_smtp_config' => [
'required', new EnumIsValidRule(SmtpEnum::class)
],
'prod_smtp_key' => 'required_if:prod_smtp_config,3|max:255|max:255',
]);
}
}

View file

@ -1,22 +0,0 @@
<?php
namespace App\Http\Requests\Projects;
class UpdateProjectRequest extends StoreProjectRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return \array_merge(parent::rules(), [
// TODO : compléter les rules .
'prod_laravel_app_key' => 'required_if:type,3',
'dev_laravel_app_key' => 'required_if:type,3',
'prod_smtp_config' => 'integer|required',
'prod_smtp_key' => 'required_if:prod_smtp_config,3'
]);
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace App\Http\Requests\Servers;
use App\Enums\Environment;
use App\Enums\ServerType;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
class StoreServerRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'env' => [
'required', new EnumIsValidRule(Environment::class)
],
'virtual_ip' => 'required|ipV4',
'ssh' => 'required',
'infraweb_ip' => 'required|ipV4',
'title' => 'required|min:2|max:50',
'name' => 'required|min:2|max:50',
'function' => 'required|min:2|max:50',
'comment' => 'nullable|string',
'type' => [
'required', new EnumIsValidRule(ServerType::class)
],
];
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Http\Requests\Servers;
class UpdateServerRequest extends StoreServerRequest
{
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Http\Requests\ServiceAccounts;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class StoreServiceAccountsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|min:2|max:255',
'url' => 'required|min:2|max:255|url',
'account' => 'required|min:2|max:255',
'password' => 'required|max:255'
];
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Http\Requests\ServiceAccounts;
class UpdateServiceAccountsRequest extends StoreServiceAccountsRequest
{
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Http\Requests\Webhooks;
use Illuminate\Foundation\Http\FormRequest;
class GitlabRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return $this->header('X-Gitlab-Token') !== null &&
$this->header('X-Gitlab-Token') === config('webhooks.gitlab.token');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'event_type' => 'required|in:merge_request'
];
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Container\Container;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
class CollectionHelper
{
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
{
$page = Paginator::resolveCurrentPage('page');
return self::paginateManually($results, $pageSize, $page);
}
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @param integer $page
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
{
$total = $results->count();
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => 'page',
]);
}
/**
* Create a new length-aware paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param integer $total
* @param integer $perPage
* @param integer $currentPage
* @param array $options
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
'items' => $items,
'total' => $total,
'perPage' => $perPage,
'currentPage' => $currentPage,
'options' => $options
]);
}
}

View file

@ -0,0 +1,190 @@
<?php
namespace App\Lib\Helpers;
class ExceptionsHelper
{
/**
* Handle sentry event
*
* @param \Sentry\Event $event
* @return null|\Sentry\Event
*/
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
{
// Remove unwanted reports.
$event->setModules([]);
$event->setOsContext(null);
/** @var \Sentry\ExceptionDataBag[] */
$exceptionBag = [];
foreach ($event->getExceptions() as $exceptionDataBag) {
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
}
$event->setExceptions($exceptionBag);
return $event;
}
/**
* Anonimize Sentry Exception bag
*
* @param \Sentry\ExceptionDataBag $exceptionDataBag
* @return \Sentry\ExceptionDataBag
*/
private static function getAnonymizedSentryException(
\Sentry\ExceptionDataBag $exceptionDataBag
): \Sentry\ExceptionDataBag {
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
}
return $exceptionDataBag;
}
/**
* Anonymise sentry stacktrace
*
* @param \Sentry\Stacktrace $stackTrace
* @return \Sentry\Stacktrace
*/
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
{
/** @var \Sentry\Frame[] */
$frames = \array_reverse($stackTrace->getFrames(), true);
$i = \count($frames) - 1;
// * Remove all but keep first (index.php).
while ($i-- > 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
foreach ($frames as $index => $frame) {
$frame->setPostContext([]);
$frame->setPreContext([]);
$frame->setContextLine('');
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
$stackTrace->addFrame($frame);
if ($index === 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
}
return $stackTrace;
}
/**
* Get anonymized sentry params
*
* @param mixed[] $params
* @return string[]
*/
private static function getAnonymizedSentryParams(array $params): array
{
foreach ($params as &$param) {
$type = \strtolower(\gettype($param));
if (\is_string($param)) {
$type = \explode(' ', \trim(strtolower($param)))[0] ?? null;
}
switch (true) {
case $type === 'lambda':
case $type === 'object':
continue 2;
case $type === 'array' and \is_array($param):
$param = sprintf('Array(%d)', \count($param));
continue 2;
default:
if (\is_numeric($param)) {
$param = \ucfirst($type);
continue 2;
}
if (\is_string($param)) {
$param = sprintf('String(%d)', \strlen($param));
continue 2;
}
} //end switch
} //end foreach
return $params;
}
/**
* Anonymize at least Sql queries
*
* @param string $message
* @return string
*/
public static function getAnonymizedMessage(string $message): string
{
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
if ($sqlPos = \strpos($message, 'to use near')) {
$message = \mb_strcut($message, 0, $sqlPos);
}
return $message;
}
/**
* Anonymize stack trace
*
* @param \Throwable $e
* @return string
*/
public static function getAnonymizedStackTrace(\Throwable $e): string
{
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
$traceStack = $e->getTrace();
$traceStr = '';
$maxStackStage = 0;
foreach ($traceStack as $stackStage => $stack) {
$maxStackStage = $stackStage;
$traceStr .= \sprintf(
'#%s %s(%d): ',
$stackStage,
str_replace($projectPath, '', $stack['file'] ?? ''),
$stack['line'] ?? ''
);
if (isset($stack['class'])) {
$traceStr .= "{$stack['class']}->{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
}
} //end foreach
$maxStackStage++;
$traceStr .= "#$maxStackStage {main}";
return $traceStr;
}
/**
* Handle Args
*
* @param array $stack
* @return string
*/
public static function handleArgs(array $stack): string
{
$traceArgs = '';
$argsCount = count($stack['args']);
for ($i = 0; $i < $argsCount; $i++) {
$type = \gettype($stack['args'][$i]);
switch ($type) {
case 'object':
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
break;
case 'array':
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
break;
case 'string':
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
break;
default:
$traceArgs .= \gettype($stack['args'][$i]);
}
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
}
return $traceArgs;
}
}

View file

@ -0,0 +1,454 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* This class is used to handle files in LDC Laravel stack
*
* * The storage strategy will be using date.
* The file name may contain sufix timestamp highresolution time (hrtime or microtime) if is duplicate.
* (function() { return time() . ' ' . Str::slug(microtime(true)) . ' ' . hrtime(true); })();
*
* posts/ - The model table name
* 10/ - Year
* 04/ - Month
* 19/ - Day
* 5f/ - First two letters of md5 file
* black-cat-under-red-car.png - Slugified file name
* black-cat-under-red-car-286071690833400.png - Slugified file name that is duplicated
*
* The final path will be storage/modelfiles/posts/10/04/19/5f/black-cat-under-red-car-286071690833400.png
*
* ! No limits of file per folder will be handled.
*
* @author Munsch Jérémy <jeremy.munsch@ldc.fr>
*/
class FileStorageHelper
{
/**
* Destroy the file associate with the attribute.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeFile(Model $model, string $attribute): void
{
$path = \ltrim(\urldecode($model->{$attribute}), '/');
if (\strpos($path, 'storage') !== 0) {
return;
}
$path = ToolboxHelper::mbReplace('storage/', '', $path);
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
}
/**
* Destroy the file associate with the attribute old value (before updated to DB).
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @param boolean $onlyIfUpdated
* @return void
*/
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
{
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
if ($onlyIfUpdated and $model->{$attribute} === ToolboxHelper::getOriginal($model, $attribute)) {
return;
}
$originalPath = \ltrim(\urldecode(ToolboxHelper::getOriginal($model, $attribute)), '/');
if (\strpos($originalPath, 'storage') !== 0) {
return;
}
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
if (Storage::disk('public')->exists($originalPath)) {
Storage::disk('public')->delete($originalPath);
}
}
/**
* Store a file using optimized storage strategy
*
* You can use filename param to overload file name.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
* @param boolean $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param boolean $private To store the file in a private space.
* @return string
* @throws \RuntimeException If file copy fails.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public static function storeFile(
Model $model,
$file,
bool $slugify = false,
string $filename = null,
bool $private = false
): string {
// phpcs:enable
$tableName = $model->getTable();
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
/** @var \Illuminate\Http\UploadedFile $file */
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
$newPath = str_replace(storage_path('app'), '', $newPath);
if (!$private) {
// * Store public file.
$file->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
$file->storeAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return str_replace('/private/', '', $newPath);
}//end if
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
if (!File::copy($file->getRealPath(), $newPath)) {
throw new \RuntimeException(sprintf(
'Failed to copy file %s to %s',
$file->getRealPath(),
$newPath
));
}
return $private ?
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
return strval($file);
}
/**
* Store all files from string attribute html
* If oldHtmlString is passed, a diff is done with new and old files
* to keep only files that are present in the new html string.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param string $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
* files using storeModelFilesFromHtml.
* @return string Html with files pathes replaced using storage strategy.
*/
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
$fileInfo = new \SplFileInfo($disk->path(
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
));
$htmlString = ToolboxHelper::mbReplace(
$path,
'/' . self::storeFile($model, $fileInfo, true),
$htmlString
);
});
if ($oldHtmlString) {
self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString);
}
return $htmlString;
}
/**
* Remove all old files from string attribute html that are stored in public that
* are nove present anymore on new html string
*
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
{
self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString));
}
/**
* Remove all old files that are stored in public that
* are not present anymore on new array files
*
* @param string[] $oldFiles The old array files.
* @param string[] $newFiles The new array files.
* @return void
*/
public static function removeOldFiles(array $oldFiles, array $newFiles): void
{
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
$filesToRemove = $oldFiles->diff($filesToKeep);
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeModelFilesOfHtml(string $htmlString): void
{
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Find all occurence of files in html
*
* @param string $html
* @return array
*/
public static function findFilesInHtml(string $html): array
{
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
return collect($m)->pluck('href')->filter()
->concat(collect($m)->pluck('url')->filter())
->concat(collect($m)->pluck('src')->filter())
->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'public' (using helper storage strategy)
*
* @param array $urls
* @return array
*/
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('public')
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'uploads'
*
* @param array $urls
* @return array
*/
private static function onlyUploadedFilesWithFileManager(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$host = \parse_url($url, \PHP_URL_HOST);
$path = \parse_url($url, \PHP_URL_PATH);
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
\strpos(\ltrim($path, '/'), 'uploads') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('uploads')
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Prepare storage path and return
* the file full path
*
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param boolean $private May the file be stored in a private dir.
* @return string
*/
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
{
// * Security filter
$filename = \htmlspecialchars($filename);
// * Security filter + beautifier
$filename = self::sanitizeFileName($filename);
// * Slugify filename
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
(\strlen($ext) ? ".{$ext}" : '');
$folderPath = self::getStoragePath(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
}
/**
* Create sub folders to store files using pattern
* storage/modelfiles/posts/10/04/19/5f
* year/month/day/md5 first 2 letters
*
* @param string $storage_dir
* @param string $table
* @param string $filename
* @return string
*/
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
{
$filenameHash = \md5($filename);
$pathFolder = \storage_path(sprintf(
'%s/%s/%s/%s/%s/%s',
$storage_dir,
$table,
gmdate('y'),
gmdate('m'),
gmdate('d'),
substr($filenameHash, 0, 2)
));
if (!File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
return $pathFolder;
}
/**
* Get unique filename if the path is already occupied
*
* @param string $folderPath
* @param string $filename
* @return string
* @throws \RuntimeException If a unique filename cannot be obtained.
*/
private static function getFileUniqueName(string $folderPath, string $filename): string
{
$attempts = 0;
$maxAttempts = 20;
while (File::exists("{$folderPath}/{$filename}")) {
if ($attempts++ >= $maxAttempts) {
throw new \RuntimeException("Failed to have a unique filename for {$folderPath}/{$filename}");
}
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
\uniqid('-') . '.' .
pathinfo($filename, \PATHINFO_EXTENSION);
}
return $filename;
}
/**
* Slugify the filename
*
* @param string $filename
* @return string
*/
public static function slugifyFileName(string $filename): string
{
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
}
/**
* Sanitize File name for uploaded files
*
* @param string $filename The original uploaded client file name.
* @param boolean $beautify Beautify the filename.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $filename, bool $beautify = true): string
{
// * Sanitize filename
// phpcs:disable Generic.Files.LineLength.TooLong
$filename = preg_replace(
'~
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
~x',
'-',
$filename
);
// phpcs:enable
// * Avoids '.' '..' or '.hiddenFiles' .
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
/** @var string */
$filename = pathinfo($filename, \PATHINFO_FILENAME);
$encoding = mb_detect_encoding($filename);
$encoding = $encoding ? $encoding : null;
return \mb_strcut(
$filename,
0,
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
$encoding
) . (!empty($extension) ? ".$extension" : '');
}
/**
* Beautify a filename
*
* @param string $filename
* @return string
*/
private static function beautifyFilename(string $filename): string
{
// Reduce consecutive characters.
$filename = preg_replace([
// * "file name.zip" becomes 'file-name.zip'
'/ +/',
// * "file___name.zip" becomes 'file-name.zip'
'/_+/',
// * "file---name.zip" becomes 'file-name.zip'
'/-+/'
], '-', $filename);
$filename = preg_replace([
// * "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// * "file...name..zip" becomes "file.name.zip"
'/\.{2,}/'
], '.', $filename);
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// * ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
}
}

View file

@ -0,0 +1,519 @@
<?php
namespace App\Lib\Helpers;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Finder\SplFileInfo;
class ToolboxHelper
{
/**
* @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2
*
* @return array<int|string, mixed>
*/
public static function arrayMergeRecursiveDistinct(array &$array1, array &$array2): array
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
/**
* Validate recaptcha
*
* @param \Illuminate\Http\Request $request
* @param boolean $isV3
* @return void
*/
public static function validateRecaptcha(Request $request, bool $isV3 = true)
{
Validator::validate($request->all(), [
'g-recaptcha-response' => [
'required', 'string',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($isV3) {
try {
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.google.recaptcha.private'),
'response' => \strval($value),
'remoteip' => app('clientIp')
]);
if ($e = $response->toException()) {
throw $e;
}
if ($isV3) {
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
!$response->json('score')
) {
throw new \RuntimeException();
}
if (\floatval($response->json('score')) < 0.8) {
$fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha");
}
return;
}
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
) {
throw new \RuntimeException();
}
} catch (\Exception $e) {
$fail('Erreur lors de la validation du formulaire veuillez réssayer');
} //end try
}
]
], [], ['g-recaptcha-response' => 'reCaptcha']);
}
/**
* Strip scheme (https) on oembed urls
* Only embed allowed are for MP4 self hosted videos right now
*
* https is force by ckeditor MediaElements which natively supports
* only cloud hosted medias (youtube, spotify etc..)
*
* @param string $input
* @return string
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
}
/**
* Assert That no Back Office url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalBoUrls',
['attribute' => $field]
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Assert That only external url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalUrls(string $field, string $text): string
{
self::assertNoInternalBoUrls($field, $text);
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalUrls',
['attribute' => $field, 'appname' => config('app.name')]
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Mutltibyte string replace
*
* @param array|string $search
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string
*/
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
int &$count = 0
): array|string {
if (!is_array($search) && is_array($replace)) {
return false;
}
if (is_array($subject)) {
// Call mb_replace for each single string in $subject .
foreach ($subject as &$string) {
$string = &self::mbReplace($search, $replace, $string, $count);
}
} elseif (is_array($search)) {
if (!is_array($replace)) {
foreach ($search as &$string) {
$subject = self::mbReplace($string, $replace, $subject, $count);
}
} else {
$n = max(count($search), count($replace));
while ($n--) {
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
next($search);
next($replace);
}
}
} else {
$parts = mb_split(preg_quote($search), $subject);
$count = count($parts) - 1;
$subject = implode($replace, $parts);
} //end if
return $subject;
}
/**
* Asserts that the field is unique
*
* @param string $table
* @param string $field
* @param string|integer|float|boolean|null|array $value
* @param integer|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
*/
public static function assertFieldIsUnique(
string $table,
string $field,
string|int|float|bool|null|array $value,
?int $id = null,
?callable $query = null
) {
if (!$query) {
Validator::make([$field => $value], [
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
self::mbReplace('.', '\.', "{$field}.unique")
=> trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value])
])->validate();
return;
}
Validator::make([$field => $value], [
$field => Rule::unique($table)->where($query)->ignore($id),
], [
$field . '.unique' => trans(
'crud.libs.toolbox.assertFieldIsUnique',
['attribute' => $field, 'value' => $value]
)
])->validate();
}
/**
* Get original polyfill with autotranslate behavior
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return mixed
*/
public static function getOriginal(Model $model, string $attribute): mixed
{
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
return $model->getTranslation(
$attribute,
$model->getLocale(),
$model->useFallbackLocale()
);
}
return $model->getOriginal($attribute);
}
/**
* Dynamic model query
*
* @param string $defaultLocale
* @param \Illuminate\Contracts\Database\Eloquent\Builder $q
* @param string $column
* @param string $value
* @return \Illuminate\Contracts\Database\Eloquent\Builder
*/
public static function queryColumnWithLocales(
string $defaultLocale,
Builder $q,
string $column,
string $value
): Builder {
$currentLocale = app()->currentLocale();
$locales = collect(config('app.locales'))
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
if (!$locales->has($defaultLocale)) {
// * Remove default locale.
$locales->pull($defaultLocale);
}
if (!$locales->has($currentLocale)) {
// * Remove current locale.
$locales->pull($currentLocale);
}
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value) {
// * Query default locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$defaultLocale}') = '{$value}'");
if ($defaultLocale !== $currentLocale) {
// * Query current locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$currentLocale}') = '{$value}'");
}
// * Query any other locale
$locales->each(fn ($locale) => $q->orWhereRaw(
"JSON_EXTRACT({$column}, '$.{$locale}') = '{$value}'"
));
});
}
/**
* Generates an array from a dot separated string
*
* @param string $path
* @param mixed $value
* @param string $separator
* @return array
*/
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
{
$arr = $value;
$keys = array_reverse(explode($separator, $path));
foreach ($keys as $key) {
$arr = [$key => $arr];
}
return $arr;
}
/**
* Convert deeply an object to array
*
* @param array|object $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
}
/**
* Validate picture
*
* @param string $attribute
* @param string|null $value
* @param callback $fail
* @param object $sizes
* @return void
*/
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes)
{
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
'attribute' => $attribute
]));
return;
}
/** @var string */
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
if (
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
'image/jpeg', 'image/jpg', 'image/png', 'image/gif'
])
) {
$fail(trans(':attribute doit être une image de type \'image/jpeg,image/jpg,image/png,image/gif\'', [
'attribute' => $attribute
]));
}
if (!($dimensions = \getimagesize($fileFullPath)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
$fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute
]));
} else {
$width = intval($dimensions[0]);
$height = intval($dimensions[1]);
$xMessage = trans(
":attribute la largeur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
);
if ($sizes->minWith === $sizes->maxWith) {
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWith}px");
}
$yMessage = trans(
":attribute la hauteur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
);
if ($sizes->minHeight === $sizes->maxHeight) {
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minWith}px");
}
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) {
$fail($xMessage);
}
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
$fail($yMessage);
}
} //end if
}
/**
* Get the file full path
*
* @param string $value
* @param boolean $useStorage
* @return string
*/
private static function getUploadedFileFullPath(string $value, bool $useStorage): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk();
return !$useStorage ? (realpath($value) ?: $value) : $disk->path($value);
}
/**
* Get the file mimetype
*
* @param string $value
* @param boolean $useStorage
* @return string|false
*/
private static function getUploadedFileMimeType(string $value, bool $useStorage)
{
return !$useStorage ? File::mimeType($value) : Storage::mimeType($value);
}
/**
* Fetch class list from a specific namespace
*
* @param string $namespace
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingNamespace(string $namespace): array
{
return collect(File::files(\app_path(Str::of($namespace)->replaceFirst('\App', ''))))
->filter(fn (SplFileInfo $splFileInfo) => $splFileInfo->getExtension() === 'php')
->map(fn (SplFileInfo $splFileInfo) => Str::of($splFileInfo->getBasename())
->replace('.' . $splFileInfo->getExtension(), '')->prepend("{$namespace}\\"))->all();
}
/**
* Get all classes from a specific namespace that implement a specific interface
*
* @param string $namespace
* @param string $interface
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingInterface(string $namespace, string $interface): array
{
return Arr::where(
static::getClassesUsingNamespace($namespace),
fn (string $class_namespace) => \collect(class_implements($class_namespace, true))->filter()
->contains(fn (string $class_interface) => $class_interface === $interface)
);
}
}

View file

@ -1,89 +0,0 @@
<?php
namespace App\Lib\LDC;
use GuzzleHttp\Client;
use Illuminate\Contracts\Foundation\Application;
use Psr\Http\Message\ResponseInterface;
class MailjetSms
{
/** @var Application $app */
protected $app;
/** @var array $config */
protected $config = [];
/**
* MailjetSms
*
* @param \Illuminate\Contracts\Foundation\Application $app
*/
public function __construct(Application $app)
{
$this->app = $app;
$this->loadDefaultOptions();
}
/**
* loadDefaultOptions
*
* @return \App\Lib\LDC\MailjetSms
*/
protected function loadDefaultOptions(): \App\Lib\LDC\MailjetSms
{
$this->config = $this->app['config']->get('services.mailjet');
return $this;
}
/**
* buildHttpClient
*
* @return \GuzzleHttp\Client
*/
protected function buildHttpClient(): \GuzzleHttp\Client
{
return new Client([
'allow_redirects' => false,
'connect_timeout' => 3,
'verify' => false,
'headers' => [
'User-Agent' => 'LDCMailjetSms/1.0',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => sprintf('Bearer %s', $this->config['sms_token']),
],
'timeout' => 5,
]);
}
/**
* send sms
*
* @param string $message
* @param string $to
* @param string|null $from
* @return \Psr\Http\Message\ResponseInterface
* @throws \GuzzleHttp\Exception\RequestException If Mailsms send service call failed.
*/
public function send(string $message, string $to, ?string $from = null): ResponseInterface
{
$from = $from ?? $this->config['sms_from'];
$client = $this->buildHttpClient();
try {
$response = $client->post($this->config['sms_endpoint'], [
'json' => [
'Text' => $message,
'From' => $from,
'To' => $to,
]
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
report($e);
throw $e;
}
return $response;
}
}

View file

@ -1,228 +0,0 @@
<?php
namespace App\Lib;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ToolboxHelper
{
/**
* Strip scheme (https) on oembed urls
* Only embed allowed are for MP4 self hosted videos right now
*
* https is force by ckeditor MediaElements which natively supports
* only cloud hosted medias (youtube, spotify etc..)
*
* @param string $input
* @return string
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
}
/**
* Assert That no Back Office url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => sprintf(
'%s : Il est interdit de coller une url du back office dans ce champ',
trans($field)
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Assert That only external url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalUrls(string $field, string $text): string
{
self::assertNoInternalBoUrls($field, $text);
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => sprintf(
'%s : Il est interdit de coller une url de %s dans ce champ',
trans($field),
config('app.name')
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Generates an array from a dot separated string
*
* @param string $path
* @param mixed $value
* @param string $separator
* @return array
*/
public static function dotStringToNestedArray(string $path, $value, string $separator = '.'): array
{
$arr = $value;
$keys = array_reverse(explode($separator, $path));
foreach ($keys as $key) {
$arr = [$key => $arr];
}
return $arr;
}
/**
* Destroy image
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeImage(Model $model, string $attribute)
{
$path = ltrim($model->{$attribute}, '/');
if (File::exists($path) and \strpos($path, 'storage') === 0) {
File::delete($path);
}
}
/**
* Destroy image
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeOldImage(Model $model, string $attribute)
{
$originalPath = ltrim($model->getOriginal($attribute), '/');
if (File::exists($originalPath) and \strpos($originalPath, 'storage') === 0) {
File::delete($originalPath);
}
}
/**
* Store an image of different types
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $image
* @return string
* @throws \ErrorException If file copy fails.
*/
public static function storeImage(Model $model, $image): string
{
$table = $model->getTable();
if (\is_object($image) and $image instanceof \Illuminate\Http\UploadedFile) {
/** @var \Illuminate\Http\UploadedFile $image */
$newPath = self::prepareMoveFile($table, \filter_var(
\strip_tags(\urldecode($image->getClientOriginalName())),
\FILTER_SANITIZE_STRING
));
$newPath = str_replace(storage_path('app'), '', $newPath);
$image->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
if (\is_object($image) and $image instanceof \SplFileInfo) {
/** @var \SplFileInfo $image */
$newPath = self::prepareMoveFile($table, $image->getBaseName());
if (!File::copy($image->getRealPath(), $newPath)) {
throw new \ErrorException(sprintf(
'Failed to copy file %s to %s',
$image->getRealPath(),
$newPath
));
}
return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
return \ltrim(strval($image), '/');
}
/**
* Prepare storage path and return
* the image full path
*
* @param string $table Model table name.
* @param string $filename The basename for the file.
* @return string
*/
private static function prepareMoveFile(string $table, string $filename): string
{
$pathFolder = \storage_path(sprintf('app/public/images/%s', $table));
if (!File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
$filename = sprintf('%s-%s', Str::uuid(), $filename);
$fullPath = sprintf('%s/%s', $pathFolder, $filename);
if (File::exists($fullPath)) {
File::delete($fullPath);
}
return $fullPath;
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace App\Mail\Webhooks;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class Gitlab extends Mailable
{
use Queueable;
use SerializesModels;
/**
* Mail data
*
* @var array
*/
public $data;
/**
* Create a new message instance.
*
* @param array $data
*/
public function __construct(array $data)
{
$this->data = $data;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->text('emails.webhooks.gitlab');
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LDCNetwork extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'ldc_networks';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'cidr', 'first_ip', 'last_ip'
];
}

View file

@ -4,7 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ProjectStatusChecker extends Model
class Network extends Model
{
/**
* The attributes that should be cast.
@ -12,17 +12,24 @@ class ProjectStatusChecker extends Model
* @var array
*/
protected $casts = [
'code' => 'array',
'sitemap' => 'boolean',
'robots' => 'boolean'
'cidr' => 'encrypted',
'first_ip' => 'encrypted',
'last_ip' => 'encrypted'
];
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'networks';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'project_uuid', 'code', 'sitemap', 'robots', 'logs', 'last_call'
'cidr', 'first_ip', 'last_ip', 'commentaire'
];
}

View file

@ -1,232 +0,0 @@
<?php
namespace App\Models;
use App\Enums\PhpVersion;
use App\Enums\ProjectType;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Project extends Model
{
use CastsEnums;
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'dev_login' => 'array',
'prod_login' => 'array',
'https' => 'boolean'
];
/**
* Map attribute names to enum classes.
*
* @var array
*/
protected $enumCasts = [
'php' => PhpVersion::class,
'type' => ProjectType::class,
'smtp' => SmtpEnum::class
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'display_name', 'php', 'type', 'https', 'prod_haproxy_id',
'dev_haproxy_id', 'prod_url', 'dev_url', 'prod_bo_url', 'dev_bo_url', 'prod_login', 'dev_login',
'prod_document_root', 'dev_document_root', 'prod_unix_user', 'dev_unix_user',
'prod_unix_pwd', 'dev_unix_pwd', 'prod_db_user', 'dev_db_user',
'prod_db_pwd', 'dev_db_pwd', 'prod_db_schema', 'dev_db_schema',
'prod_db_server_id', 'dev_db_server_id', 'prod_web_server_id',
'dev_web_server_id', 'prod_comment', 'dev_comment', 'prod_eai',
'dev_eai', 'prod_whitelist_fw', 'dev_whitelist_fw',
'dev_htaccess_user', 'dev_htaccess_pwd', 'prod_ga_id',
'prod_laravel_app_key', 'dev_laravel_app_key',
'prod_smtp_config', 'prod_smtp_key',
];
/**
* Get the route key for the model.
*
* @return string
*/
public function getRouteKeyName(): string
{
return 'uuid';
}
// * METHODS
/**
* get dev_web_ip attribute
*
* @return mixed
*/
public function getDevWebIpAttribute()
{
$value = Server::find($this->dev_web_server_id);
if ($value) {
$value = $value->ssh;
}
return $value;
}
/**
* get dev_db_ip attribute
*
* @return mixed
*/
public function getDevDbIpAttribute()
{
$value = Server::find($this->dev_db_server_id);
if ($value) {
$value = $value->ssh;
}
return $value;
}
/**
* get prod_web_ip attribute
*
* @return mixed
*/
public function getProdWebIpAttribute()
{
$value = Server::find($this->prod_web_server_id);
if ($value) {
$value = $value->ssh;
}
return $value;
}
/**
* get prod_db_ip attribute
*
* @return mixed
*/
public function getProdDbIpAttribute()
{
$value = Server::find($this->prod_db_server_id);
if ($value) {
$value = $value->ssh;
}
return $value;
}
/**
* get checked attribute
*
* @return mixed
*/
public function getCheckedAttribute()
{
$value = ProjectStatusChecker::select('last_call')->where('project_uuid', '=', $this->uuid)->first();
if (!$value) {
$value = 'missing';
} else {
$value = $value->last_call;
$dt = Carbon::now();
if ($dt->diffInMinutes($value) > 30) {
$value = 'false';
} else {
$value = 'true';
}
}
return $value;
}
/**
* get sitemap_checked attribute
*
* @return mixed
*/
public function getSitemapCheckedAttribute()
{
$value = ProjectStatusChecker::select('sitemap')->where('project_uuid', '=', $this->uuid)->first();
if (!$value) {
$value = 'missing';
} else {
$value = $value->sitemap;
}
return $value;
}
/**
* get robots_checked attribute
*
* @return mixed
*/
public function getRobotsCheckedAttribute()
{
$value = ProjectStatusChecker::select('robots')->where('project_uuid', '=', $this->uuid)->first();
if (!$value) {
$value = 'missing';
} else {
$value = $value->robots;
}
return $value;
}
/**
* get logs_checked attribute
*
* @return mixed
*/
public function getLogsCheckedAttribute()
{
$value = ProjectStatusChecker::select('logs')->where('project_uuid', '=', $this->uuid)->first();
if (!$value) {
$value = 'missing';
} else {
$value = $value->logs;
}
return $value;
}
/**
* get code attribute
*
* @return mixed
*/
public function getCodeAttribute()
{
$value = ProjectStatusChecker::select('code')->where('project_uuid', '=', $this->uuid)->first();
if (!$value) {
$value = 'missing';
} else {
$value = $value->code;
}
return $value;
}
/**
* get logs_hr attribute
*
* @return string
*/
public function getLogsHrAttribute(): string
{
$value = ProjectStatusChecker::select('logs')->where('project_uuid', '=', $this->uuid)->first();
return $this->humanFilesize($value->logs);
}
/**
* Human readable file size
*
* @param float $size
* @return string
*/
private function humanFilesize(float $size): string
{
$i = \floatval(floor(log($size, 1024)));
return round($size / pow(1024, $i), [0,0,2,2,3][$i]) . [' O',' Ko',' Mo',' Go',' To'][$i];
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace App\Models;
use App\Enums\Environment;
use App\Enums\ServerType;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Server extends Model
{
use CastsEnums;
/**
* Map attribute names to enum classes.
*
* @var array
*/
protected $enumCasts = [
'env' => Environment::class,
'type' => ServerType::class
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'env', 'virtual_ip',
'ssh', 'infraweb_ip',
'title', 'name', 'function',
'comment',
'type'
];
}

View file

@ -1,17 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServiceAccount extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'account', 'password', 'url'
];
}

View file

@ -2,43 +2,64 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use HasFactory;
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
* @var array<int, string>
*/
protected $fillable = [
'firstname',
'lastname',
'email',
'phone',
'password',
'equipe_web_member'
];
/**
* The attributes that should be hidden for arrays.
* The attributes that should be hidden for serialization.
*
* @var array
* @var array<int, string>
*/
protected $hidden = [
'password', 'remember_token',
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
* The attributes that should be cast.
*
* @var array
* @var array<string, string>
*/
protected $casts = [
'equipe_web_member' => 'boolean',
'email_verified_at' => 'datetime',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
// * EVENT CLOSURES.
static::creating(function (self $model) {
if (Hash::needsRehash($model->password)) {
$model->password = Hash::make($model->password);
}
});
static::updating(function (self $model) {
if (Hash::needsRehash($model->password)) {
$model->password = Hash::make($model->password);
}
});
}
}

View file

@ -4,11 +4,21 @@ namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
}
/**
* Bootstrap any application services.
*
@ -16,34 +26,45 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
// * HTTPS FIX
URL::forceScheme('https');
// * LOCAL FORCE eg: for Carbon
// * Remove numeric to prevent float seperator to breaking numeric transactions
\collect([\LC_TIME, \LC_MESSAGES, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY])
->map(function ($contant) {
setlocale(
$contant,
\sprintf(
'%s_%s.UTF-8',
config('app.locale'),
\strtoupper(config('app.locale'))
)
);
// * HA PROXY FIX
App::singleton('clientIp', function () {
if (config('app.env') === 'local') {
return request()->getClientIp();
} else {
return request()->server('HTTP_X_FORWARDED_FOR') ??
request()->getClientIp();
}
});
// * LOCAL FORCE eg: for Carbon
setlocale(LC_ALL, \sprintf(
'%s_%s.UTF-8',
config('app.locale'),
\strtoupper(config('app.locale'))
));
Carbon::setLocale(config('app.locale'));
// * Force bootstrap paginator
Paginator::useBootstrap();
$this->sharetoView();
}
// * HA PROXY FIX
app()->singleton('clientIp', function () {
if (
config('app.env') === 'local'
) {
return request()->getClientIp();
/**
* Share Vars to view
*
* @return void
*/
private function sharetoView()
{
if (!app()->runningInConsole()) {
// * FORCE BOOTSTRAP PAGINATOR (or custom if in front)
// Early boot, request wont be filled as expected.
if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') {
Paginator::useBootstrap();
} else {
Paginator::useBootstrap();
}
return request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
});
}
}
}

View file

@ -7,7 +7,7 @@ use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvid
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
@ -18,8 +18,7 @@ class AuthServiceProvider extends ServiceProvider
*
* @return void
*/
public function boot()
public function boot(): void
{
$this->registerPolicies();
}
}

View file

@ -2,23 +2,20 @@
namespace App\Providers;
use Aacotroneo\Saml2\Events\Saml2LoginEvent;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Session;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
* The event to listener mappings for the application.
*
* @var array
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
@ -27,71 +24,17 @@ class EventServiceProvider extends ServiceProvider
*
* @return void
*/
public function boot()
public function boot(): void
{
parent::boot();
}
Event::listen('Aacotroneo\Saml2\Events\Saml2LoginEvent', function (Saml2LoginEvent $event) {
$messageId = $event->getSaml2Auth()->getLastMessageId();
if (class_exists('Memcached')) {
$cache = new \Memcached();
$cache->addServer(
config('cache.stores.memcached.servers.0.host'),
config('cache.stores.memcached.servers.0.port')
);
$fromCache = $cache->get(config('cache.prefix') . "_saml_message_id_" . $messageId);
if (!in_array($cache->getResultCode(), [\Memcached::RES_SUCCESS, \Memcached::RES_NOTFOUND])) {
throw new \Exception(
\sprintf(
"SAML MESSAGE ID HANDLER - Memcached Error : %d - %s",
$cache->getResultCode(),
"https://www.php.net/manual/en/memcached.getresultcode.php"
)
);
}
if ($fromCache) {
logger()->error(
'SAML MESSAGE ID HANDLER',
[
'error' => 'Message ID : ' . $messageId . " replayed !"
]
);
abort(403);
} else {
// Cache for 8h.
$cache->set(config('cache.prefix') . "_saml_message_id_" . $messageId, $messageId, 28800);
if ($cache->getResultCode() === \Memcached::RES_NOTSTORED) {
throw new \Exception('SAML MESSAGE ID HANDLER - Memcached Error : NOT STORED');
}
}
} else {
throw new \Exception("SAML MESSAGE ID HANDLER - Memcached is missing");
} //end if
$user = $event->getSaml2User();
$email = $user->getNameId();
$lUser = User::where('email', '=', $email)->first();
if (!$lUser) {
abort(redirect()->route('unauthorized'));
}
session(['sessionIndex' => $user->getSessionIndex()]);
session(['tmpUserOTP' => $lUser]);
abort(redirect()->route('auth.verify'));
});
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
Event::listen('Aacotroneo\Saml2\Events\Saml2LogoutEvent', function ($event) {
Auth::logout();
Session::save();
});
/**
* Determine if events and listeners should be automatically discovered.
*
* @return boolean
*/
public function shouldDiscoverEvents(): bool
{
return false;
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Lib\LDC\MailjetSms;
class MailjetSmsServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$this->app->singleton('MailjetSms', function ($app) {
return new MailjetSms($app);
});
}
}

View file

@ -11,23 +11,25 @@ use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to the "home" route for your application.
* The path to your application's "home" route.
*
* This is used by Laravel authentication to redirect users after login.
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/';
/**
* Define your route model bindings, pattern filters, etc.
* Define your route model bindings, pattern filters, and other route configuration.
*
* @return void
*/
public function boot()
public function boot(): void
{
$this->configureRateLimiting();
$this->cspReportRoute();
$this->routes(function () {
Route::middleware('web')
->namespace($this->namespace)
@ -48,13 +50,52 @@ class RouteServiceProvider extends ServiceProvider
*
* @return void
*/
protected function configureRateLimiting()
protected function configureRateLimiting(): void
{
RateLimiter::for('web', function (Request $request) {
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
});
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
return Limit::perMinute(150)->by($request->user()?->id ?: app('clientIp'));
});
RateLimiter::for('web', function (Request $request) {
return Limit::perMinute(150)->by($request->user()?->id ?: app('clientIp'));
});
RateLimiter::for('credentials', function (Request $request) {
return Limit::perMinute(30)->by($request->user()?->id ?: app('clientIp'));
});
}
/**
* * Register CSP report URI
*
* Put this function inside RouteServiceProvider,
* and call it in RouteServiceProvider::boot
*
* @return void
*/
private function cspReportRoute()
{
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
\Illuminate\Support\Facades\Route::post('/csp-report', function () {
$isCspRequest = request()->header('Content-Type') === 'application/csp-report';
if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) {
abort(401);
}
// * Flatten if needed
$cspReport = collect(request()->json('csp-report'))
->flatMap(function ($v, $k) {
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
})->all();
// * Filter array keys
$cspReport = collect(array_intersect_key($cspReport, [
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
'referrer' => '', 'violated-directive' => ''
]));
\Illuminate\Support\Facades\Log::warning(sprintf(
"CSP REPORT for %s : \n %s",
$cspReport->get('document-uri'),
$cspReport->implode("\n")
));
})->middleware('throttle:5,2')
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
->name('cspReportUri');
}
}

View file

@ -6,6 +6,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\Finder\SplFileInfo;
class TranslationServiceProvider extends ServiceProvider
{
@ -14,7 +15,7 @@ class TranslationServiceProvider extends ServiceProvider
*
* @return void
*/
public function boot()
public function boot(): void
{
foreach ($this->collectLocalesStrings() as $locale) {
// * Suported locales
@ -31,12 +32,12 @@ class TranslationServiceProvider extends ServiceProvider
/**
* Gather provided langs checking from files
*
* @return Collection
* @return \Illuminate\Support\Collection<string, string>
*/
private function collectLocalesStrings(): Collection
{
return collect(File::allFiles(resource_path('lang/')))->flatMap(function ($file) {
if ($file->getRelativePath()) {
return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) {
if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) {
return [$file->getRelativePath() => ''];
}
})->keys();
@ -46,7 +47,7 @@ class TranslationServiceProvider extends ServiceProvider
* Gather all php translations
*
* @param string $locale
* @return array
* @return array<string, string>
*/
private function phpTranslations(string $locale): array
{
@ -62,7 +63,7 @@ class TranslationServiceProvider extends ServiceProvider
* Gather json translations
*
* @param string $locale
* @return array
* @return array<string, string>
*/
private function jsonTranslations(string $locale): array
{

View file

@ -14,7 +14,7 @@ class CidrRule implements Rule
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function passes($attribute, $value)
public function passes(mixed $attribute, mixed $value)
{
// phpcs:enable
$m = true;

View file

View file

@ -0,0 +1,259 @@
<?php
namespace App\Traits\Enums;
use Illuminate\Support\Str;
/**
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
trait BaseEnum
{
// * GENERIC METHODS
/**
* Get enum value
*
* @return string|integer
*/
public function value(): string|int
{
return self::toValues()[$this->name];
}
/**
* Get enum label
*
* @return string
*/
public function label(): string
{
return self::toLabels()[$this->name];
}
/**
* Get enum name
*
* @return string
*/
public function name(): string
{
return $this->name;
}
// * STATIC FETCH METHODS
/**
* Get enum labels
*
* @return array<string>
*/
public static function toLabels(): array
{
if (empty(self::getCache('labels'))) {
$labels = self::getLabelsConstant();
self::setCache('labels', \collect(self::cases())
->mapWithKeys(function ($enum) use ($labels) {
if (!\is_null($labels) and !isset($labels[$enum->name])) {
throw new \Error('Missing definition label ' . self::class . " for {$enum->name}");
}
return [$enum->name => \trans(
!\is_null($labels) ? $labels[$enum->name] : Str::ucfirst($enum->name)
)];
})->all());
}
return self::getCache('labels');
}
/**
* Get enum values
*
* @return array<integer|string>
*/
public static function toValues(): array
{
if (empty(self::getCache('values'))) {
self::setCache('values', \collect(self::cases())
->mapWithKeys(fn ($enum) => [$enum->name => $enum->value])->all());
}
return self::getCache('values');
}
/**
* Get enums
*
* @return array
*/
public static function all(): array
{
return self::cases();
}
/**
* Get enums
*
* @return array
*/
public static function toArray(): array
{
if (empty(self::getCache('enums'))) {
$definitions = self::getCustomDefinitions();
self::setCache('enums', \collect(self::cases())
->mapWithKeys(function ($enum) use ($definitions) {
$baseArray = [
'name' => $enum->name,
'label' => $enum->label(),
'value' => $enum->value,
];
foreach ($definitions as $index => $elements) {
$index = Str::lower(Str::singular($index));
if (!isset($elements[$enum->name])) {
throw new \Error("Missing definition {$index} " . self::class . " for {$enum->name}");
}
$baseArray[$index] = $elements[$enum->name];
}
return [$enum->name => (object)$baseArray];
})->all());
}
return self::getCache('enums');
}
/**
* Fetch enum using value
*
* @param \App\Traits\Enums\BaseEnum|integer|string $value
* @return \App\Traits\Enums\BaseEnum|null
*/
public static function make(self|int|string $value): self|null
{
return !\is_object($value) ?
\collect(self::cases())->keyBy('value')->get($value) : $value;
}
/**
* Test if 2 enum are equals
*
* @param object ...$others
* @return boolean
* @throws \Error If provided element are not enums taht extends BaseEnum.
*/
public function equals(object ...$others): bool
{
foreach ($others as $other) {
if (!\in_array(BaseEnum::class, \class_uses_recursive($other))) {
throw new \Error(__METHOD__ .
' requires $enum to use ' . BaseEnum::class);
}
if (
get_class($this) === get_class($other)
&& $this->value === $other->value
) {
return true;
}
}
return false;
}
/**
* Serialize to json
*
* @return mixed
*/
public function jsonSerialize(): mixed
{
return $this->value;
}
// * CACHE
/**
* Get cache array
*
* @param string $index
* @return array
*/
private static function getCache(string $index): array
{
self::initCache();
if (!isset($GLOBALS[self::class]['props'][$index])) {
$GLOBALS[self::class]['props'][$index] = [];
}
return $GLOBALS[self::class]['props'][$index];
}
/**
* Set cache array
*
* @param string $index
* @param array $items
* @return void
*/
private static function setCache(string $index, array $items): void
{
self::initCache();
if (!isset($GLOBALS[self::class]['props'][$index])) {
$GLOBALS[self::class]['props'][$index] = [];
}
$GLOBALS[self::class]['props'][$index] = $items;
}
/**
* Init cache if needed
*
* @return void
*/
private static function initCache(): void
{
if (!isset($GLOBALS[self::class])) {
$GLOBALS[self::class] = [
'props' => [
'enums' => [],
'values' => [],
'labels' => []
],
'customProps' => null
];
}
}
// * PRIVATE
/**
* Fetch the LABELS constant if exists
*
* @return array|null
*/
private static function getLabelsConstant(): array|null
{
$class_name = self::class;
try {
$constant_reflex = new \ReflectionClassConstant($class_name, 'LABELS');
return $constant_reflex->getValue();
} catch (\ReflectionException $e) {
return null;
}
}
/**
* Get custom constants
*
* @return array
*/
private static function getCustomDefinitions(): array
{
// ? Has custom props in cache ?
if (!isset($GLOBALS[self::class]) or \is_null($GLOBALS[self::class]['customProps'])) {
$names = \collect(self::cases())->keyBy('name');
$GLOBALS[self::class]['customProps'] = \collect((new \ReflectionClass(self::class))->getConstants())
->filter(function (mixed $constant, string $key) use ($names) {
if ($key === 'LABELS' or !\is_array($constant)) {
return false;
}
return \collect($constant)->keys()
->filter(fn (string $name) => $names->has($name))->count();
})->all();
}
return $GLOBALS[self::class]['customProps'];
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace App\Traits\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
/**
* ! Use this only on limited elements, (not on tags, or products, or posts for ex.)
*
* @property private static array $cacheConditions Eloquent condition to cache elements (to be used in where statement).
*/
trait IsCachedAsCollection
{
/**
* Get all available instances from cache.
* If cache was reset, it will fetch them from DB.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getCached(): Collection
{
if (!Cache::has(static::getCacheKeyName())) {
try {
static::setCached();
} catch (QueryException $e) {
report($e);
return \collect();
}
}
return Cache::get(static::getCacheKeyName());
}
/**
* Remove cached elements
*
* @return void
*/
public static function removeCached(): void
{
Cache::forget(static::getCacheKeyName());
}
/**
* Put all instances into cache.
*
* @return void
*/
protected static function setCached(): void
{
$conditions = [];
if (property_exists(get_called_class(), 'cacheConditions')) {
$conditions = collect(static::${'cacheConditions'})->all();
}
Cache::put(static::getCacheKeyName(), static::queryCached(static::query())
->where($conditions)->get()
->pipe(fn (Collection $collection) => \call_user_func([self::class, 'filterQuery'], $collection)));
}
/**
* Change query for cached elements
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected static function queryCached(Builder $query): Builder
{
return $query;
}
/**
* Change query for cached elements
*
* @param \Illuminate\Support\Collection $results
* @return \Illuminate\Support\Collection
*/
protected static function filterQuery(Collection $results): Collection
{
return $results;
}
/**
* Get the class basename
*
* @return string
*/
protected static function getCacheKeyName(): string
{
return static::getCallClassBaseName();
}
/**
* Get the class basename
*
* @return string
*/
protected static function getCallClassBaseName(): string
{
return str_replace(__NAMESPACE__ . '\\', '', get_called_class());
}
}

View file

@ -0,0 +1,180 @@
<?php
namespace App\Traits\Models;
use App\Enums\InputType;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
trait MiniCrudModel
{
/**
* Fill the model with an array of attributes.
*
* @param array $attributes
* @return void
* @throws \Illuminate\Database\Eloquent\MassAssignmentException If fill prop is not allowed.
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
*/
public function fill(array $attributes)
{
// phpcs:enable
parent::fill($attributes);
if (isset($attributes['data'])) {
$this->setData($attributes['data']);
}
}
/**
* Apply function on data typed text while on save event
*
* @return void
*/
public function minicrudCreating()
{
/** @var array<string,array> */
$data = $this->data;
foreach ($data as &$input) {
switch ($input['inputType']) {
case InputType::text->value:
$input['text'] = clean($input['text']);
$input['text'] = FileStorageHelper::storeModelFilesFromHtml($this, $input['text']);
break;
} //end switch
} //end foreach
$this->data = $data;
}
/**
* Apply function on data typed text while on update event
*
* @return void
*/
public function minicrudUpdating()
{
$this->minicrudCreating();
}
/**
* Apply function on data typed text while on updated event
*
* @return void
*/
public function minicrudUpdated()
{
/** @var array */
foreach ($this->data as $index => $input) {
switch ($input['inputType']) {
case InputType::text->value:
$original = ToolboxHelper::getOriginal($this, 'data');
FileStorageHelper::removeOldModelFilesFromHtml(
$input['text'],
$original[$index]['text'] ?? ''
);
break;
} //end switch
} //end foreach
}
/**
* Apply function on data typed text while on deleted event
*
* @return void
*/
public function minicrudDeleted()
{
/** @var array */
foreach ($this->data as $input) {
switch ($input['inputType']) {
case InputType::text->value:
FileStorageHelper::removeModelFilesOfHtml($input['text']);
break;
} //end switch
} //end foreach
}
/**
* Set a specific prop
*
* @param array $data
* @return void
*/
private function setData(array $data): void
{
$attrs = $this->attributes;
if (isset($attrs['data'])) {
unset($attrs['data']);
}
$this->setAttribute('data', $data);
}
/**
* Set a specific prop
*
* @param array $value
* @return void
*/
protected function setDataAttribute(array $value): void
{
$this->attributes['data'] = $this->castAttributeAsJson(
'data',
$this->serializeData($value)
);
}
/**
* Set a specific prop
*
* @param string|null $data
* @return array
*/
protected function getDataAttribute(string|null $data): array
{
return $this->fromJson($data ?? $this->attributes['data'] ?? '[]');
}
/**
* Serialize properties
*
* @param array $data
* @return array
* @throws \ErrorException If input type is no handled.
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
private function serializeData(array $data): array
{
foreach ($data as $o => $input) {
switch ($input['inputType']) {
case InputType::image->value:
$data[$o]['value'] = \preg_replace(
'/^\/|^/',
'',
FileStorageHelper::storeFile($this, $input['value'], true)
);
break;
case InputType::selectEnum->value:
case InputType::boolean->value:
case InputType::password->value:
case InputType::email->value:
case InputType::number->value:
case InputType::videoVimeo->value:
case InputType::videoYoutube->value:
case InputType::video->value:
break;
case InputType::string->value:
case InputType::text->value:
case InputType::url->value:
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
break;
default:
throw new \ErrorException(sprintf(
'Unknown input Type %s',
$input['inputType']
));
} //end switch
} //end foreach
return $data;
}
}

View file

2
artisan Normal file → Executable file
View file

@ -11,7 +11,7 @@ define('LARAVEL_START', microtime(true));
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
| loading of any of our classes manually. It's great to relax.
|
*/

View file

@ -8,44 +8,30 @@
],
"license": "MIT",
"require": {
"php": "^7.3",
"aacotroneo/laravel-saml2": "^2.1",
"fideloper/proxy": "^4.0",
"fruitcake/laravel-cors": "^2.2",
"guzzlehttp/guzzle": "^7.0.1",
"kwaadpepper/enum": "^2.1",
"kwaadpepper/exception-handler": "^0.0.8",
"laravel/framework": "^8.75",
"laravel/tinker": "^2.5",
"laravel/ui": "^3.0",
"lfkeitel/phptotp": " 1.0",
"mews/purifier": "^3.3",
"propaganistas/laravel-phone": "^4.3",
"simplesoftwareio/simple-qrcode": "^4.2",
"slevomat/coding-standard": "^7.0"
"php": "^8.1",
"diglactic/laravel-breadcrumbs": "^8.0",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.0",
"laravel/tinker": "^2.8",
"laravel/ui": "^4.2",
"ldc/web-admin-secure-laravel": "^2.0",
"lfkeitel/phptotp": "^1.1",
"mews/purifier": "^3.4",
"sentry/sentry-laravel": "*",
"uwil/k8s-laravel": "^1.2"
},
"require-dev": {
"doctrine/dbal": "^3.3",
"facade/ignition": "^2.5",
"fakerphp/faker": "^1.9.1",
"laravel/sail": "^1.0.1",
"kitloong/laravel-migrations-generator": "^6.8",
"krlove/eloquent-model-generator": "dev-l10-compatibility",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^5.10",
"phpunit/phpunit": "^9.5.10",
"slevomat/coding-standard": "^7.2"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"extra": {
"laravel": {
"dont-discover": []
}
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "^8.10",
"spatie/laravel-ignition": "^2.0",
"thedoctor0/laravel-factory-generator": "^1.2"
},
"autoload": {
"psr-4": {
@ -54,23 +40,51 @@
"Database\\Seeders\\": "database/seeders/"
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/laravel-shift/eloquent-model-generator.git"
},
{
"type": "composer",
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
}
],
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
}
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

7399
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
<?php
return [
'key' => 'CE8S52Qq7stf5k8frF4T'
'key' => env('LDC_WEB_API_KEY')
];

View file

@ -1,5 +1,7 @@
<?php
use Illuminate\Support\Facades\Facade;
return [
/*
@ -54,7 +56,7 @@ return [
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL', null),
'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
@ -123,6 +125,23 @@ return [
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => 'file'
],
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
@ -175,8 +194,7 @@ return [
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\TranslationServiceProvider::class,
App\Providers\MailjetSmsServiceProvider::class,
App\Providers\TranslationServiceProvider::class
],
@ -191,44 +209,6 @@ return [
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'SmtpEnum' => App\Enums\Smtp::class,
],
'aliases' => Facade::defaultAliases()->merge([])->toArray(),
];

View file

@ -14,7 +14,7 @@ return [
*/
'defaults' => [
'guard' => 'web',
'guard' => 'frontend',
'passwords' => 'users',
],
@ -31,20 +31,20 @@ return [
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
| Supported: "session"
|
*/
'guards' => [
'web' => [
'backend' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'frontend' => [
'driver' => 'session',
// ! Remplacer "users" par le model authenticable voulu
'provider' => 'users',
'hash' => false,
],
],
@ -81,16 +81,20 @@ return [
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],

Some files were not shown because too many files have changed in this diff Show more