Merge pull request #7680 from betagouv/saml-with-metadata
update saml idp. Utilise une gem permettant l'intégration saml avec Dolist
This commit is contained in:
commit
9c27128c8b
11 changed files with 143 additions and 35 deletions
2
Gemfile
2
Gemfile
|
@ -72,7 +72,7 @@ gem 'rake-progressbar', require: false
|
||||||
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
|
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
|
||||||
gem 'rgeo-geojson'
|
gem 'rgeo-geojson'
|
||||||
gem 'rqrcode'
|
gem 'rqrcode'
|
||||||
gem 'ruby-saml-idp'
|
gem 'saml_idp'
|
||||||
gem 'sanitize-url'
|
gem 'sanitize-url'
|
||||||
gem 'sassc-rails' # Use SCSS for stylesheets
|
gem 'sassc-rails' # Use SCSS for stylesheets
|
||||||
gem 'sentry-delayed_job'
|
gem 'sentry-delayed_job'
|
||||||
|
|
16
Gemfile.lock
16
Gemfile.lock
|
@ -633,13 +633,18 @@ GEM
|
||||||
ruby-graphviz (1.2.5)
|
ruby-graphviz (1.2.5)
|
||||||
rexml
|
rexml
|
||||||
ruby-progressbar (1.11.0)
|
ruby-progressbar (1.11.0)
|
||||||
ruby-saml-idp (0.3.5)
|
|
||||||
ruby-vips (2.1.4)
|
ruby-vips (2.1.4)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
ruby_parser (3.15.1)
|
ruby_parser (3.15.1)
|
||||||
sexp_processor (~> 4.9)
|
sexp_processor (~> 4.9)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.0)
|
||||||
|
saml_idp (0.14.0)
|
||||||
|
activesupport (>= 5.2)
|
||||||
|
builder (>= 3.0)
|
||||||
|
nokogiri (>= 1.6.2)
|
||||||
|
rexml
|
||||||
|
xmlenc (>= 0.7.1)
|
||||||
sanitize (6.0.0)
|
sanitize (6.0.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
|
@ -780,6 +785,13 @@ GEM
|
||||||
websocket-driver (0.7.5)
|
websocket-driver (0.7.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
|
xmlenc (0.8.0)
|
||||||
|
activemodel (>= 3.0.0)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
|
nokogiri (>= 1.6.0, < 2.0.0)
|
||||||
|
xmlmapper (>= 0.7.3)
|
||||||
|
xmlmapper (0.8.1)
|
||||||
|
nokogiri (~> 1.11)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.0)
|
zeitwerk (2.6.0)
|
||||||
|
@ -889,7 +901,7 @@ DEPENDENCIES
|
||||||
rubocop-performance
|
rubocop-performance
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
ruby-saml-idp
|
saml_idp
|
||||||
sanitize-url
|
sanitize-url
|
||||||
sassc-rails
|
sassc-rails
|
||||||
scss_lint
|
scss_lint
|
||||||
|
|
|
@ -1,28 +1,38 @@
|
||||||
class SamlIdpController < ActionController::Base
|
class SamlIdpController < ActionController::Base
|
||||||
include SamlIdp::Controller
|
include SamlIdp::Controller
|
||||||
|
|
||||||
before_action :validate_saml_request
|
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
if validate_saml_request
|
||||||
|
render template: 'saml_idp/new'
|
||||||
|
else
|
||||||
|
head :forbidden
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render xml: SamlIdp.metadata.signed
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
if validate_saml_request
|
||||||
if super_admin_signed_in?
|
if super_admin_signed_in?
|
||||||
@saml_response = encode_SAMLResponse(current_super_admin.email, saml_attributes)
|
@saml_response = idp_make_saml_response(current_super_admin)
|
||||||
render :template => "saml_idp/idp/saml_post", :layout => false
|
render template: 'saml_idp/saml_post', layout: false
|
||||||
else
|
else
|
||||||
redirect_to root_path, alert: t("errors.messages.saml_not_authorized")
|
redirect_to root_path, alert: t("errors.messages.saml_not_authorized")
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
head :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
def metadata
|
|
||||||
render layout: false, content_type: "application/xml", formats: :xml
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def saml_attributes
|
def idp_make_saml_response(super_admin)
|
||||||
admin_attributes = %[<saml:AttributeStatement><saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"><saml:AttributeValue>#{current_super_admin.email}</saml:AttributeValue></saml:Attribute><saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"><saml:AttributeValue>ds|#{current_super_admin.id}</saml:AttributeValue></saml:Attribute></saml:AttributeStatement>]
|
encode_response super_admin, encryption: {
|
||||||
{
|
cert: saml_request.service_provider.cert,
|
||||||
issuer_uri: saml_auth_url,
|
block_encryption: 'aes256-cbc',
|
||||||
attributes_provider: admin_attributes
|
key_transport: 'rsa-oaep-mgf1p'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,4 +28,7 @@ as defined by the routes in the `admin/` namespace
|
||||||
<% if Rails.application.secrets.sendinblue[:enabled] && ENV["SAML_IDP_ENABLED"] == "enabled" %>
|
<% if Rails.application.secrets.sendinblue[:enabled] && ENV["SAML_IDP_ENABLED"] == "enabled" %>
|
||||||
<%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %>
|
<%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if ENV.fetch("SAML_IDP_ENABLED") == "enabled" && ENV["DOLIST_LOGIN_URL"].present? %>
|
||||||
|
<%= link_to "Dolist", ENV.fetch("DOLIST_LOGIN_URL"), class: "navigation__link", target: '_blank' %>
|
||||||
|
<% end %>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
13
app/views/saml_idp/new.html.erb
Normal file
13
app/views/saml_idp/new.html.erb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
</head>
|
||||||
|
<body onload="document.forms[0].submit();" style="visibility:hidden;">
|
||||||
|
<%= form_tag do %>
|
||||||
|
<%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
|
||||||
|
<%= hidden_field_tag("RelayState", params[:RelayState]) %>
|
||||||
|
<% end %>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
app/views/saml_idp/saml_post.html.erb
Normal file
14
app/views/saml_idp/saml_post.html.erb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
</head>
|
||||||
|
<body onload="document.forms[0].submit();" style="visibility:hidden;">
|
||||||
|
<%= form_tag(saml_acs_url) do %>
|
||||||
|
<%= hidden_field_tag("SAMLResponse", @saml_response) %>
|
||||||
|
<%= hidden_field_tag("RelayState", params[:RelayState]) %>
|
||||||
|
<%= submit_tag "Submit" %>
|
||||||
|
<% end %>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -44,10 +44,8 @@ FOG_OPENSTACK_URL=""
|
||||||
FOG_OPENSTACK_REGION=""
|
FOG_OPENSTACK_REGION=""
|
||||||
DS_PROXY_URL=""
|
DS_PROXY_URL=""
|
||||||
|
|
||||||
# SAML Identity provider
|
# SAML
|
||||||
SAML_IDP_ENABLED="disabled"
|
SAML_IDP_ENABLED="disabled"
|
||||||
SAML_IDP_CERTIFICATE=""
|
|
||||||
SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n"
|
|
||||||
|
|
||||||
# External service: authentication through France Connect
|
# External service: authentication through France Connect
|
||||||
FC_PARTICULIER_ID=""
|
FC_PARTICULIER_ID=""
|
||||||
|
|
|
@ -148,3 +148,10 @@ DATAGOUV_API_KEY="thisisasecret"
|
||||||
DATAGOUV_API_URL="https://www.data.gouv.fr/api/1"
|
DATAGOUV_API_URL="https://www.data.gouv.fr/api/1"
|
||||||
DATAGOUV_DESCRIPTIF_DEMARCHES_DATASET="datasetid"
|
DATAGOUV_DESCRIPTIF_DEMARCHES_DATASET="datasetid"
|
||||||
DATAGOUV_DESCRIPTIF_DEMARCHES_RESOURCE="resourceid"
|
DATAGOUV_DESCRIPTIF_DEMARCHES_RESOURCE="resourceid"
|
||||||
|
|
||||||
|
# SAML
|
||||||
|
SAML_IDP_CERTIFICATE="idpcertificate"
|
||||||
|
SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n"
|
||||||
|
SAML_DOLIST_CERTIFICATE="spcertificate"
|
||||||
|
SAML_DOLIST_HOST="dolisthoname"
|
||||||
|
DOLIST_LOGIN_URL="https://clientpreprod.dolist.net"
|
||||||
|
|
|
@ -2,6 +2,30 @@
|
||||||
# So we fetch env var directly here
|
# So we fetch env var directly here
|
||||||
|
|
||||||
if ENV['SAML_IDP_ENABLED'] == 'enabled'
|
if ENV['SAML_IDP_ENABLED'] == 'enabled'
|
||||||
SamlIdp.config.x509_certificate = ENV.fetch("SAML_IDP_CERTIFICATE")
|
SamlIdp.configure do |config|
|
||||||
SamlIdp.config.secret_key = ENV.fetch("SAML_IDP_SECRET_KEY")
|
config.base_saml_location = "https://#{ENV['APP_HOST']}/saml/metadata"
|
||||||
|
config.x509_certificate = ENV.fetch("SAML_IDP_CERTIFICATE")
|
||||||
|
config.secret_key = ENV.fetch("SAML_IDP_SECRET_KEY")
|
||||||
|
|
||||||
|
config.name_id.formats = {
|
||||||
|
"1.1" => {
|
||||||
|
email_address: -> (principal) { principal.email }
|
||||||
|
},
|
||||||
|
"2.0" => {
|
||||||
|
transient: -> (principal) { principal.email },
|
||||||
|
persistent: -> (p) { p.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service_providers = {
|
||||||
|
"https://#{ENV.fetch('SAML_DOLIST_HOST')}" => {
|
||||||
|
response_hosts: [ENV.fetch('SAML_DOLIST_HOST')],
|
||||||
|
cert: ENV.fetch("SAML_DOLIST_CERTIFICATE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.service_provider.finder = -> (entity_id) do
|
||||||
|
service_providers[entity_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
get '/saml/auth' => 'saml_idp#new'
|
get '/saml/auth' => 'saml_idp#new'
|
||||||
post '/saml/auth' => 'saml_idp#create'
|
post '/saml/auth' => 'saml_idp#create'
|
||||||
get '/saml/metadata' => 'saml_idp#metadata'
|
get '/saml/metadata' => 'saml_idp#show'
|
||||||
|
|
||||||
#
|
#
|
||||||
# Manager
|
# Manager
|
||||||
|
|
|
@ -1,7 +1,33 @@
|
||||||
describe SamlIdpController do
|
describe SamlIdpController do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(SamlIdpController).to receive(:validate_saml_request).and_return(valid_saml_request)
|
||||||
|
end
|
||||||
|
|
||||||
describe '#new' do
|
describe '#new' do
|
||||||
let(:action) { get :new }
|
let(:action) { get :new }
|
||||||
|
|
||||||
|
context 'with invalid saml request' do
|
||||||
|
let(:valid_saml_request) { false }
|
||||||
|
it { expect(action).to have_http_status(:forbidden) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with valid saml request' do
|
||||||
|
let(:valid_saml_request) { true }
|
||||||
|
|
||||||
|
it { expect(action).to have_http_status(:ok) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
describe '#create' do
|
||||||
|
let(:action) { post :create }
|
||||||
|
|
||||||
|
context 'with invalid saml request' do
|
||||||
|
let(:valid_saml_request) { false }
|
||||||
|
it { expect(action).to have_http_status(:forbidden) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with valid saml request' do
|
||||||
|
let(:valid_saml_request) { true }
|
||||||
|
|
||||||
context 'without superadmin connected' do
|
context 'without superadmin connected' do
|
||||||
it { expect(action).to redirect_to root_path }
|
it { expect(action).to redirect_to root_path }
|
||||||
|
|
||||||
|
@ -16,9 +42,10 @@ describe SamlIdpController do
|
||||||
before { sign_in superadmin }
|
before { sign_in superadmin }
|
||||||
|
|
||||||
it 'encode saml response' do
|
it 'encode saml response' do
|
||||||
expect(subject).to receive(:encode_SAMLResponse).with(superadmin.email, anything())
|
expect(subject).to receive(:idp_make_saml_response).with(superadmin)
|
||||||
action
|
action
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue