From b024dbd007fe19825d17f2c64232377ccda9737a Mon Sep 17 00:00:00 2001 From: Vincent BRIDIER Date: Thu, 25 Jul 2024 17:29:19 +0200 Subject: [PATCH 1/3] Fix SAML without CSP on login + idp pem loading --- .env.example | 3 - .gitignore | 1 + manifest.yaml | 2 - routes/web-bo.php | 246 +++++++++++++++++++++++----------------------- 4 files changed, 125 insertions(+), 127 deletions(-) diff --git a/.env.example b/.env.example index ec58631..35eb445 100644 --- a/.env.example +++ b/.env.example @@ -53,6 +53,3 @@ GITLAB_TOKEN= SAML_SP_URL_LIST="" ## json sp config SAML_SP_CONFIG='' -## IDP CERT -SAMLIDP_KEY= -SAMLIDP_CERT= diff --git a/.gitignore b/.gitignore index f9a0745..3ce0771 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ Homestead.json Homestead.yaml npm-debug.log yarn-error.log +storage/samlidp/* diff --git a/manifest.yaml b/manifest.yaml index f4d7043..0a1b774 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -25,8 +25,6 @@ data: SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}" GITLAB_URL: "https://${CI_SERVER_HOST}" GITLAB_TOKEN: "${APP_GITLAB_TOKEN}" - SAMLIDP_KEY: "${APP_SAMLIDP_KEY}" - SAMLIDP_CERT: "${APP_SAMLIDP_CERT}" SAML_SP_URL_LIST: "${APP_SAML_SP_URL_LIST}" SAML_SP_CONFIG: "${APP_SAML_SP_CONFIG}" diff --git a/routes/web-bo.php b/routes/web-bo.php index bd7f4b3..878c254 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -16,6 +16,7 @@ use App\Http\Controllers\Bo\SiteController; use App\Http\Controllers\Bo\TicketController; use App\Http\Controllers\Bo\TicketTypeController; use App\Http\Controllers\Bo\UserController; +use App\Http\Middleware\SecureHeaders; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; @@ -23,141 +24,142 @@ Route::middleware('admin-secure') ->group(function () { Route::namespace('\App\Http\Controllers\Bo') + ->withoutMiddleware([SecureHeaders::class]) ->group(function () { - Auth::routes( - [ - 'login' => true, - 'logout' => true, - 'register' => false, - 'reset' => true, - 'confirm' => false, - 'verify' => false - ] - ); + Auth::routes( + [ + 'login' => true, + 'logout' => true, + 'register' => false, + 'reset' => true, + 'confirm' => false, + 'verify' => false + ] + ); }); Route::name('bo.') ->group(function () { Route::middleware('auth:backend') - ->group(function () { - // * HOME - Route::get('/', [DashboardController::class, 'index'])->name('dashboard'); + ->group(function () { + // * HOME + Route::get('/', [DashboardController::class, 'index'])->name('dashboard'); - Route::middleware(['can:isAdmin']) - ->group(function () { - // * RESOURCES - Route::resource('users', UserController::class)->except('show'); - Route::resource('informations', InformationController::class)->except('show'); - Route::resource('ticket_types', TicketTypeController::class) - ->only(['index', 'edit', 'update']) - ->withoutMiddleware('htmlsanitize') - ->middleware('htmlsanitize:template') - ->parameters(['ticket_types' => 'ticket_type']); - Route::resource('packages', PackageController::class)->except('show'); - Route::resource('dutyhours', DutyhourController::class)->except('show'); - Route::resource('invoices', InvoiceController::class) - ->withoutMiddleware('htmlsanitize') - ->middleware(['htmlsanitize:comment']); - Route::resource('companies', CompanyController::class)->except('show'); - Route::resource('brands', BrandController::class); - Route::resource('brands.credit_accounts', AccountController::class) - ->only(['index', 'create', 'store']) - ->parameters(['credit_accounts' => 'account']); - Route::resource('brands.credit_accounts.credit_logs', CreditLogController::class) - ->only(['index', 'create', 'store']) - ->parameters(['credit_accounts' => 'account', 'credit_logs' => 'credit_log']); - Route::resource('labels', LabelController::class)->except('show'); + Route::middleware(['can:isAdmin']) + ->group(function () { + // * RESOURCES + Route::resource('users', UserController::class)->except('show'); + Route::resource('informations', InformationController::class)->except('show'); + Route::resource('ticket_types', TicketTypeController::class) + ->only(['index', 'edit', 'update']) + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:template') + ->parameters(['ticket_types' => 'ticket_type']); + Route::resource('packages', PackageController::class)->except('show'); + Route::resource('dutyhours', DutyhourController::class)->except('show'); + Route::resource('invoices', InvoiceController::class) + ->withoutMiddleware('htmlsanitize') + ->middleware(['htmlsanitize:comment']); + Route::resource('companies', CompanyController::class)->except('show'); + Route::resource('brands', BrandController::class); + Route::resource('brands.credit_accounts', AccountController::class) + ->only(['index', 'create', 'store']) + ->parameters(['credit_accounts' => 'account']); + Route::resource('brands.credit_accounts.credit_logs', CreditLogController::class) + ->only(['index', 'create', 'store']) + ->parameters(['credit_accounts' => 'account', 'credit_logs' => 'credit_log']); + Route::resource('labels', LabelController::class)->except('show'); - // * INVOICES - Route::get('/invoices/json/paginate', [InvoiceController::class, 'jsonPaginate']) - ->name('invoices.json-paginate'); - Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download']) - ->name('invoices.download'); + // * INVOICES + Route::get('/invoices/json/paginate', [InvoiceController::class, 'jsonPaginate']) + ->name('invoices.json-paginate'); + Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download']) + ->name('invoices.download'); - // * PROJECTS - Route::resource('projects', ProjectController::class); - Route::get('/projects/json/paginate', [ProjectController::class, 'jsonPaginate']) - ->name('projects.json-paginate'); + // * PROJECTS + Route::resource('projects', ProjectController::class); + Route::get('/projects/json/paginate', [ProjectController::class, 'jsonPaginate']) + ->name('projects.json-paginate'); - // * SITES - Route::resource('sites', SiteController::class)->except('index'); - Route::get('/sites/json/paginate', [SiteController::class, 'jsonPaginate']) - ->name('sites.json-paginate'); + // * SITES + Route::resource('sites', SiteController::class)->except('index'); + Route::get('/sites/json/paginate', [SiteController::class, 'jsonPaginate']) + ->name('sites.json-paginate'); - // * TICKETS - Route::get('/tickets/json/paginate', [TicketController::class, 'jsonPaginate']) - ->name('tickets.json-paginate'); - Route::post('/ticket/{ticket}/create-issue', [TicketController::class, 'createGitlabIssue']) - ->middleware('can:createGitlabIssue,ticket') - ->name('ticket.create-issue'); - }); - Route::resource('credit_accounts', AccountController::class) - ->only(['index']) - ->parameters(['credit_accounts' => 'account']); - Route::resource('credit_accounts.credit_logs', CreditLogController::class) - ->only(['index']) - ->parameters(['credit_accounts' => 'account', 'credit_logs' => 'credit_log']); - - // * USERS - Route::get('/users/change-password', [UserController::class, 'passwordReset']) - ->name('users.passwordReset'); - Route::post('/users/change-password', [UserController::class, 'passwordResetSubmit']) - ->name('users.passwordResetSubmit'); - - // * SITES - Route::resource('sites', SiteController::class)->only('index'); - - // * TICKETS - Route::resource('ticket', TicketController::class)->except(['destroy']); - Route::prefix('/ticket') - ->name('ticket.') - ->group(function () { - // * Estimation - Route::name('estimation.')->group(function () { - Route::get('/{ticket}/estimate', [EstimationController::class, 'estimate']) - ->middleware('can:estimate,ticket') - ->name('estimate'); - Route::post('/{ticket}/estimate', [EstimationController::class, 'estimateSubmit']) - ->middleware('can:estimate,ticket') - ->name('estimateSubmit'); - Route::post('/{ticket}/estimate/answer', [ - EstimationController::class, 'estimatePendingSubmit' - ]) - ->middleware('can:validateEstimate,ticket') - ->name('estimatePendingSubmit'); - Route::post('/{ticket}/estimate/cancel', [ - EstimationController::class, - 'estimateCancelSubmit' - ]) - ->middleware('can:cancelEstimate,ticket') - ->name('estimateCancelSubmit'); + // * TICKETS + Route::get('/tickets/json/paginate', [TicketController::class, 'jsonPaginate']) + ->name('tickets.json-paginate'); + Route::post('/ticket/{ticket}/create-issue', [TicketController::class, 'createGitlabIssue']) + ->middleware('can:createGitlabIssue,ticket') + ->name('ticket.create-issue'); }); + Route::resource('credit_accounts', AccountController::class) + ->only(['index']) + ->parameters(['credit_accounts' => 'account']); + Route::resource('credit_accounts.credit_logs', CreditLogController::class) + ->only(['index']) + ->parameters(['credit_accounts' => 'account', 'credit_logs' => 'credit_log']); - Route::post('/{ticket}/assignTo', [TicketController::class, 'assignTo']) - ->middleware('can:assignTo,ticket') - ->name('assignTo'); - Route::post('/{ticket}/reopen', [TicketController::class, 'reopen']) - ->middleware('can:reopen,ticket') - ->name('reopen'); - Route::post('/{ticket}/status', [TicketController::class, 'changeStatus']) - ->middleware('can:changeStatus,ticket') - ->name('changeStatus'); - Route::post('/{ticket}/subscribe', [TicketController::class, 'subscribe']) - ->middleware('can:suscribeToTicket,ticket') - ->name('subscribe'); - Route::put('/{ticket}/archive', [TicketController::class, 'setToArchive']) - ->middleware('can:changeStatus,ticket') - ->name('archive'); - Route::put('/{ticket}/message/create', [TicketController::class, 'createMessage']) - ->middleware('can:createMessage,ticket') - ->name('message.create'); - Route::put('/{ticket}/message/{message}/update', [ - TicketController::class, - 'updateMessage' - ])->middleware('can:updateMessage,ticket,message') - ->name('message.update'); - }); - }); + // * USERS + Route::get('/users/change-password', [UserController::class, 'passwordReset']) + ->name('users.passwordReset'); + Route::post('/users/change-password', [UserController::class, 'passwordResetSubmit']) + ->name('users.passwordResetSubmit'); + + // * SITES + Route::resource('sites', SiteController::class)->only('index'); + + // * TICKETS + Route::resource('ticket', TicketController::class)->except(['destroy']); + Route::prefix('/ticket') + ->name('ticket.') + ->group(function () { + // * Estimation + Route::name('estimation.')->group(function () { + Route::get('/{ticket}/estimate', [EstimationController::class, 'estimate']) + ->middleware('can:estimate,ticket') + ->name('estimate'); + Route::post('/{ticket}/estimate', [EstimationController::class, 'estimateSubmit']) + ->middleware('can:estimate,ticket') + ->name('estimateSubmit'); + Route::post('/{ticket}/estimate/answer', [ + EstimationController::class, 'estimatePendingSubmit' + ]) + ->middleware('can:validateEstimate,ticket') + ->name('estimatePendingSubmit'); + Route::post('/{ticket}/estimate/cancel', [ + EstimationController::class, + 'estimateCancelSubmit' + ]) + ->middleware('can:cancelEstimate,ticket') + ->name('estimateCancelSubmit'); + }); + + Route::post('/{ticket}/assignTo', [TicketController::class, 'assignTo']) + ->middleware('can:assignTo,ticket') + ->name('assignTo'); + Route::post('/{ticket}/reopen', [TicketController::class, 'reopen']) + ->middleware('can:reopen,ticket') + ->name('reopen'); + Route::post('/{ticket}/status', [TicketController::class, 'changeStatus']) + ->middleware('can:changeStatus,ticket') + ->name('changeStatus'); + Route::post('/{ticket}/subscribe', [TicketController::class, 'subscribe']) + ->middleware('can:suscribeToTicket,ticket') + ->name('subscribe'); + Route::put('/{ticket}/archive', [TicketController::class, 'setToArchive']) + ->middleware('can:changeStatus,ticket') + ->name('archive'); + Route::put('/{ticket}/message/create', [TicketController::class, 'createMessage']) + ->middleware('can:createMessage,ticket') + ->name('message.create'); + Route::put('/{ticket}/message/{message}/update', [ + TicketController::class, + 'updateMessage' + ])->middleware('can:updateMessage,ticket,message') + ->name('message.update'); + }); + }); // * Session Ping Route::get('/ping', function () { From 738cfb7aea63a87cb0559bd8109bd5ac3af66dd8 Mon Sep 17 00:00:00 2001 From: Vincent BRIDIER Date: Fri, 26 Jul 2024 15:05:16 +0200 Subject: [PATCH 2/3] PHPCS --- routes/web-bo.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routes/web-bo.php b/routes/web-bo.php index 878c254..fd29849 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -89,7 +89,10 @@ Route::middleware('admin-secure') // * TICKETS Route::get('/tickets/json/paginate', [TicketController::class, 'jsonPaginate']) ->name('tickets.json-paginate'); - Route::post('/ticket/{ticket}/create-issue', [TicketController::class, 'createGitlabIssue']) + Route::post( + '/ticket/{ticket}/create-issue', + [TicketController::class, 'createGitlabIssue'] + ) ->middleware('can:createGitlabIssue,ticket') ->name('ticket.create-issue'); }); From 889ee1e7f41ad001a065132c5ef732e990545881 Mon Sep 17 00:00:00 2001 From: Vincent BRIDIER Date: Fri, 26 Jul 2024 15:06:30 +0200 Subject: [PATCH 3/3] Update SP management for SAML --- .env.example | 6 ---- app/Interfaces/SpsConfigInterface.php | 42 ++++++++++++++++++++++ app/Traits/Enums/SpsConfigTrait.php | 52 +++++++++++++++++++++++++++ config/samlidp.php | 14 +++++--- manifest.yaml | 2 -- storage/samlidp/config.php | 25 +++++++++++++ 6 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 app/Interfaces/SpsConfigInterface.php create mode 100644 app/Traits/Enums/SpsConfigTrait.php create mode 100644 storage/samlidp/config.php diff --git a/.env.example b/.env.example index 35eb445..0152884 100644 --- a/.env.example +++ b/.env.example @@ -47,9 +47,3 @@ LDC_WEB_CIDR_MANUAL_ALLOWED_LIST= # GITLAB API GITLAB_URL= GITLAB_TOKEN= - -# SAML IDP -## URL list separated by comma -SAML_SP_URL_LIST="" -## json sp config -SAML_SP_CONFIG='' diff --git a/app/Interfaces/SpsConfigInterface.php b/app/Interfaces/SpsConfigInterface.php new file mode 100644 index 0000000..1e87835 --- /dev/null +++ b/app/Interfaces/SpsConfigInterface.php @@ -0,0 +1,42 @@ +destination()); + } + /** + * destination method + * Return acs url concatenated with url + * + * @return string + */ + public function destination(): string + { + return $this->url() . "/saml2/support/acs"; + } + /** + * logout method + * Return sls url concatenated with url + * + * @return string + */ + public function logout(): string + { + return $this->url() . "/saml2/support/sls"; + } + /** + * certificate method + * Return certificate (.pem) file from kebab case of Enum name + * + * @return string + */ + public function certificate(): string + { + return 'file://' . storage_path( + sprintf( + 'samlidp/%s.pem', + str()->kebab($this->name) + ) + ); + } +} diff --git a/config/samlidp.php b/config/samlidp.php index dacd96b..b26958d 100644 --- a/config/samlidp.php +++ b/config/samlidp.php @@ -1,8 +1,14 @@ url()] = $sp->url(); + $sp_configs[$sp->acsBase64()] = [ + 'destination' => $sp->destination(), + 'logout' => $sp->logout(), + 'certificate' => $sp->certificate() + ]; } return [ @@ -55,7 +61,7 @@ return [ // 'encrypt_assertion' => false // ]. // ],. - 'sp' => json_decode(env('SAML_SP_CONFIG', "{}"), true), + 'sp' => $sp_configs, // If you need to redirect after SLO depending on SLO initiator // Key is beginning of HTTP_REFERER value from SERVER, value is redirect path. diff --git a/manifest.yaml b/manifest.yaml index 0a1b774..e5ce0be 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -25,8 +25,6 @@ data: SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}" GITLAB_URL: "https://${CI_SERVER_HOST}" GITLAB_TOKEN: "${APP_GITLAB_TOKEN}" - SAML_SP_URL_LIST: "${APP_SAML_SP_URL_LIST}" - SAML_SP_CONFIG: "${APP_SAML_SP_CONFIG}" --- apiVersion: v1 diff --git a/storage/samlidp/config.php b/storage/samlidp/config.php new file mode 100644 index 0000000..c30ac2d --- /dev/null +++ b/storage/samlidp/config.php @@ -0,0 +1,25 @@ + 'https://le-gaulois-pro.s-web.test' + }; + } +}