diff --git a/Gemfile b/Gemfile index 57758031..06fd1e2a 100644 --- a/Gemfile +++ b/Gemfile @@ -90,5 +90,10 @@ gem 'yt', '~> 0.28.0' # Simple HTTP client. gem 'faraday' + +# For SAML authentication +gem 'omniauth-saml' + # For device detection to determine BigBlueButton client. gem 'browser' + diff --git a/Gemfile.lock b/Gemfile.lock index d014a5be..4055c774 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -163,6 +163,9 @@ GEM omniauth-oauth2 (1.5.0) oauth2 (~> 1.1) omniauth (~> 1.2) + omniauth-saml (1.8.1) + omniauth (~> 1.3) + ruby-saml (~> 1.4, >= 1.4.3) omniauth-twitter (1.2.1) json (~> 1.3) omniauth-oauth (~> 1.1) @@ -213,6 +216,8 @@ GEM ffi (>= 0.5.0, < 2) request_store (1.4.0) rack (>= 1.4) + ruby-saml (1.4.3) + nokogiri (>= 1.5.10) rubyntlm (0.3.4) sass (3.5.5) sass-listen (~> 4.0.0) @@ -302,6 +307,7 @@ DEPENDENCIES omniauth (= 1.6.1) omniauth-google-oauth2 (= 0.4.1) omniauth-ldap + omniauth-saml omniauth-twitter (= 1.2.1) paperclip (~> 5.1) pg diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 65abc7a6..6a259706 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -23,6 +23,10 @@ class SessionsController < ApplicationController if Rails.application.config.omniauth_ldap redirect_to "#{relative_root}/auth/ldap" end + #If SAML is enabled, just route to it instead. + if Rails.application.config.omniauth_saml + redirect_to "#{relative_root}/auth/saml" + end end def create diff --git a/app/models/user.rb b/app/models/user.rb index 798e24d1..cf3f966d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -55,6 +55,14 @@ class User < ApplicationRecord auth_hash['info']['email'] end + def self.saml_username(auth_hash) + auth_hash['info']['nickname'] + end + + def self.saml_email(auth_hash) + auth_hash['info']['email'] + end + def set_encrypted_id self.encrypted_id = "#{username[0..1]}-#{Digest::SHA1.hexdigest(uid+provider)[0..7]}" end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index ae03ce8c..0c673906 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -1,4 +1,4 @@ -Rails.application.config.providers = [:google, :twitter, :ldap] +Rails.application.config.providers = [:google, :twitter, :ldap, :saml] Rails.application.config.omniauth_google = ENV['GOOGLE_OAUTH2_ID'].present? && ENV['GOOGLE_OAUTH2_SECRET'].present? @@ -6,6 +6,8 @@ Rails.application.config.omniauth_twitter = ENV['TWITTER_ID'].present? && ENV['T Rails.application.config.omniauth_ldap = ENV['LDAP_SERVER'].present? && ENV['LDAP_UID'].present? && ENV['LDAP_BASE'].present? && ENV['LDAP_BIND_DN'].present? && ENV['LDAP_PASSWORD'].present? +Rails.application.config.omniauth_saml = ENV['SAML_ISSUER'].present? && ENV['SAML_IDP_URL'].present? && ENV['SAML_IDP_CERT_FINGERPRINT'].present? + Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, ENV['TWITTER_ID'], ENV['TWITTER_SECRET'] provider :google_oauth2, ENV['GOOGLE_OAUTH2_ID'], ENV['GOOGLE_OAUTH2_SECRET'], @@ -22,6 +24,18 @@ Rails.application.config.middleware.use OmniAuth::Builder do base: ENV['LDAP_BASE'], bind_dn: ENV['LDAP_BIND_DN'], password: ENV['LDAP_PASSWORD'] + provider :saml, + issuer: ENV['SAML_ISSUER'], + idp_sso_target_url: ENV['SAML_IDP_URL'], + idp_cert_fingerprint: ENV['SAML_IDP_CERT_FINGERPRINT'], + name_identifier_format: ENV['SAML_NAME_IDENTIFIER'] || "urn:mace:dir:attribute-def:eduPersonPrincipalName", + attribute_statements: { \ + nickname: [ENV['SAML_USERNAME_ATTRIBUTE'] || 'urn:mace:dir:attribute-def:eduPersonPrincipalName'],\ + email: [ENV['SAML_EMAIL_ATTRIBUTE'] || 'urn:mace:dir:attribute-def:mail'], \ + last_name: [ENV['SAML_LASTNAME_ATTRIBUTE'] || 'urn:mace:dir:attribute-def:sn'], \ + first_name: [ENV['SAML_FIRTSNAME_ATTRIBUTE'] || 'urn:mace:dir:attribute-def:givenName'],\ + name: [ENV['SAML_COMMOMNAME_ATTRIBUTE'] || 'urn:mace:dir:attribute-def:cn'] }, + uid_attribute: ENV['SAML_UID_ATTRIBUTE'] || "urn:mace:dir:attribute-def:uid" end # Redirect back to login in development mode. diff --git a/sample.env b/sample.env index 2fdb1dc2..6d4281d1 100644 --- a/sample.env +++ b/sample.env @@ -72,6 +72,34 @@ LDAP_BASE= LDAP_BIND_DN= LDAP_PASSWORD= +# SAML2 Login Provider (optional) +# +# You can use SAML authentication by providing the values below. +# SAML_ISSUER is the name of your application. Some identity providers might need this to establish the identity of the service provider requesting the login. +# The location of this SP's metadata can be used here; For example : https://bigbluebutton.yourdomain.tld/auth/saml/metadata +# SAML_IDP_URL is the URL to which the authentication request should be sent. This would be on the identity provider. It can be found in the +# IDP's metadata in the tag +# SAML_IDP_CERT_FINFERPRINT is the fingerprint of the certificate used by the IDP, for example "25:72:85:66:C9:94:22:98:36:84:11:E1:88:C7:AC:40:98:F9:E7:82". +# You can get the fingerprint by downloading the IDP's certificate and running : +# openssl x509 -noout -in torproject.pem -fingerprint -sha1 +# SAML_NAME_IDENTIFIER describes the format of the username required by this application. +# If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress". See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 +# for other options. Note that the identity provider might not support all options. If not specified, the IdP is free to choose the name identifier format used in the response +# SAML_...._ATTRIBUTE : Attributes from the SAML response should be mapped to the attributes used by greenlight. The defaults are based upon https://wiki.surfnet.nl/display/surfconextdev/Attributes+in+SURFconext +# +# The information about this SP (metadata) can be found on your server http:///auth/saml/metadata +# +# SAML_ISSUER= +# SAML_IDP_URL= +# SAML_IDP_CERT_FINGERPRINT= +# SAML_NAME_IDENTIFIER= +# SAML_UID_ATTRIBUTE= +# SAML_USERNAME_ATTRIBUTE= +# SAML_EMAIL_ATTRIBUTE= +# SAML_LASTNAME_ATTRIBUTE= +# SAML_FIRTSNAME_ATTRIBUTE= +# SAML_COMMOMNAME_ATTRIBUTE= + # If "true", GreenLight will register a webhook callback for each meeting # created. This callback is called for all events that happen in the meeting, # including the processing of its recording. These events are used to update