From b15868fb3c37b050d10c5a2963c2105e2b07c416 Mon Sep 17 00:00:00 2001 From: Jesus Federico Date: Fri, 5 Apr 2019 14:54:36 -0400 Subject: [PATCH] GRN-80: Allow local accounts on multitenant (#428) * Changed the way the omniauth providers are declared * Allow local authentication for multitenant mode based on customer settings * Cleanead up code mandated by rubocop * Completed implementation for signin and added the one for signup * Fixed issue with rubocop * Renamed customer_name to lb_user * Renamed lb_user -> user_domain, fixed issue with signup controller, email verification WAS NOT implemented * Completed implementation of email_verification * Fixed rubocop issue * Final update * Fix for test with loadbalancer * Make sure loadbalancer mockup is only used when env defined * Fix for test on rooms_controller * Fixed most of the test failing on multitenant env * Fixed issue detected by rubocop * Fixed issue with activation tockens not working on resend * Fixed new issue found by rubocop * Updated travis script * Harcoded credentials for mockup * Updated expectation on start_session * Fixed issue with duplication of home room * Updated script for rubocop * Restored Gemfile --- .rubocop.yml | 6 +- .travis.yml | 2 +- Gemfile | 1 - .../account_activations_controller.rb | 8 +- app/controllers/application_controller.rb | 21 ++-- app/controllers/password_resets_controller.rb | 2 +- app/controllers/rooms_controller.rb | 4 +- app/controllers/sessions_controller.rb | 19 ++-- app/controllers/users_controller.rb | 42 +++----- app/helpers/application_helper.rb | 13 +++ app/helpers/sessions_helper.rb | 18 ++-- app/models/concerns/api_concern.rb | 71 -------------- app/models/room.rb | 3 + app/models/user.rb | 43 +++++---- app/views/shared/_header.html.erb | 26 ++--- .../shared/components/_resend_button.html.erb | 2 +- config/application.rb | 20 ++-- config/database.yml | 4 +- config/environments/test.rb | 8 ++ .../application_controller_renderer.rb | 2 +- config/initializers/omniauth.rb | 64 ++++++++----- config/locales/en.yml | 1 + config/routes.rb | 2 +- lib/bbb_api.rb | 82 ++++++++++++++++ spec/controllers/sessions_controller_spec.rb | 96 ++++++++++--------- spec/models/room_spec.rb | 11 +-- spec/models/user_spec.rb | 46 ++++----- spec/spec_helper.rb | 30 ++++-- 28 files changed, 354 insertions(+), 293 deletions(-) create mode 100644 lib/bbb_api.rb diff --git a/.rubocop.yml b/.rubocop.yml index c249f85a..0e5d326b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -409,6 +409,9 @@ Style/StringLiteralsInInterpolation: - single_quotes - double_quotes +Style/Strip: + Enabled: true + Layout/SpaceAroundBlockParameters: EnforcedStyleInsidePipes: no_space SupportedStylesInsidePipes: @@ -1092,9 +1095,6 @@ Performance/FixedSize: Performance/FlatMap: EnabledForFlattenWithoutParams: false -Performance/LstripRstrip: - Enabled: true - Performance/RangeInclude: Enabled: true diff --git a/.travis.yml b/.travis.yml index b83e18ac..4ea74d2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ jobs: - stage: test name: rspec - script: bundle exec rspec + script: gem install bundler & bundle exec rspec if: env(CD_TEST_IGNORE) IS NOT present - stage: build diff --git a/Gemfile b/Gemfile index 245a0094..7da2a440 100644 --- a/Gemfile +++ b/Gemfile @@ -78,7 +78,6 @@ end group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platform: :mri - # Environment configuration. gem 'dotenv-rails' end diff --git a/app/controllers/account_activations_controller.rb b/app/controllers/account_activations_controller.rb index cb7515c9..9e7eee79 100644 --- a/app/controllers/account_activations_controller.rb +++ b/app/controllers/account_activations_controller.rb @@ -27,7 +27,7 @@ class AccountActivationsController < ApplicationController # GET /account_activations/edit def edit - if @user && !@user.email_verified? && @user.authenticated?(:activation, params[:token]) + if @user && !@user.activated? && @user.authenticated?(:activation, params[:token]) @user.activate flash[:success] = I18n.t("verify.activated") + " " + I18n.t("verify.signin") @@ -40,7 +40,7 @@ class AccountActivationsController < ApplicationController # GET /account_activations/resend def resend - if @user.email_verified + if @user.activated? flash[:alert] = I18n.t("verify.already_verified") else begin @@ -67,10 +67,10 @@ class AccountActivationsController < ApplicationController end def email_params - params.require(:email).permit(:token) + params.require(:email).permit(:email, :token) end def find_user - @user = User.find_by!(email: params[:email], provider: "greenlight") + @user = User.find_by!(email: params[:email], provider: @user_domain) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4dc368a5..cd40cd46 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -23,6 +23,7 @@ class ApplicationController < ActionController::Base before_action :migration_error? before_action :set_locale + before_action :set_user_domain # Force SSL for loadbalancer configurations. before_action :redirect_to_https @@ -68,16 +69,11 @@ class ApplicationController < ActionController::Base # Determines if the BigBlueButton endpoint is configured (or set to default). def bigbluebutton_endpoint_default? - return false if loadbalanced_configuration? + return false if Rails.configuration.loadbalanced_configuration Rails.configuration.bigbluebutton_endpoint_default == Rails.configuration.bigbluebutton_endpoint end helper_method :bigbluebutton_endpoint_default? - def loadbalanced_configuration? - Rails.configuration.loadbalanced_configuration - end - helper_method :loadbalanced_configuration? - def recording_thumbnails? Rails.configuration.recording_thumbnails end @@ -106,6 +102,17 @@ class ApplicationController < ActionController::Base end def redirect_to_https - redirect_to protocol: "https://" if loadbalanced_configuration? && request.headers["X-Forwarded-Proto"] == "http" + if Rails.configuration.loadbalanced_configuration && request.headers["X-Forwarded-Proto"] == "http" + redirect_to protocol: "https://" + end end + + def set_user_domain + @user_domain = if Rails.env.test? || !Rails.configuration.loadbalanced_configuration + "greenlight" + else + parse_user_domain(request.env["SERVER_NAME"]) + end + end + helper_method :set_user_domain end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb index df7e700f..a3e5c037 100644 --- a/app/controllers/password_resets_controller.rb +++ b/app/controllers/password_resets_controller.rb @@ -84,7 +84,7 @@ class PasswordResetsController < ApplicationController # Confirms a valid user. def valid_user - unless current_user&.email_verified && current_user.authenticated?(:reset, params[:id]) + unless current_user&.activated? && current_user.authenticated?(:reset, params[:id]) redirect_to root_url end end diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index 234d8811..f6a2e6c9 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -224,12 +224,12 @@ class RoomsController < ApplicationController def validate_verified_email if current_user - redirect_to account_activation_path(current_user) unless current_user.email_verified + redirect_to account_activation_path(current_user) unless current_user.activated? end end def verify_room_owner_verified - unless @room.owner.email_verified + unless @room.owner.activated? flash[:alert] = t("room.unavailable") if current_user diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 96128b7b..62c8f613 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -27,18 +27,13 @@ class SessionsController < ApplicationController # POST /users/login def create - user = User.find_by(email: session_params[:email]) - if user && !user.greenlight_account? - redirect_to root_path, alert: I18n.t("invalid_login_method") - elsif user.try(:authenticate, session_params[:password]) - if user.email_verified - login(user) - else - redirect_to(account_activation_path(email: user.email)) && return - end - else - redirect_to root_path, alert: I18n.t("invalid_credentials") - end + user = User.find_by(email: session_params[:email], provider: @user_domain) + redirect_to(root_path, alert: I18n.t("invalid_user")) && return unless user + redirect_to(root_path, alert: I18n.t("invalid_login_method")) && return unless user.greenlight_account? + redirect_to(root_path, alert: I18n.t("invalid_credentials")) && return unless user.try(:authenticate, + session_params[:password]) + redirect_to(account_activation_path(email: user.email)) && return unless user.activated? + login(user) end # GET/POST /auth/:provider/callback diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 32e5f5bf..aea7b4ba 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -28,40 +28,24 @@ class UsersController < ApplicationController return unless Rails.configuration.allow_user_signup @user = User.new(user_params) - @user.provider = "greenlight" + @user.provider = @user_domain - # Check if user already exists - if User.exists?(email: user_params[:email], provider: @user.provider) - existing_user = User.find_by!(email: user_params[:email], provider: @user.provider) - if Rails.configuration.enable_email_verification && !existing_user.email_verified? - # User exists but is not verified - redirect_to(account_activation_path(email: existing_user.email)) && return - else - # User already exists and is verified - # Attempt to save so that the correct errors appear - @user.save + # Handle error on user creation. + render(:new) && return unless @user.save - render(:new) && return - end - elsif Rails.configuration.enable_email_verification && @user.save - begin - @user.send_activation_email(verification_link) - rescue => e - logger.error "Error in email delivery: #{e}" - flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error")) - else - flash[:success] = I18n.t("email_sent") - end + # Sign in automatically if email verification is disabled. + login(@user) && return unless Rails.configuration.enable_email_verification - redirect_to(root_path) && return - elsif @user.save - # User doesn't exist and email verification is turned off - @user.activate - login(@user) + # Start email verification and redirect to root. + begin + @user.send_activation_email(verification_link) + rescue => e + logger.error "Error in email delivery: #{e}" + flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error")) else - # Handle error on user creation. - render(:new) && return + flash[:success] = I18n.t("email_sent") end + redirect_to(root_path) end # GET /signup diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fb4886f6..f8b99126 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -16,8 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License along # with BigBlueButton; if not, see . +require 'bbb_api' + module ApplicationHelper include MeetingsHelper + include BbbApi # Gets all configured omniauth providers. def configured_providers @@ -71,4 +74,14 @@ module ApplicationHelper markdown.render(text).html_safe end + + def allow_greenlight_accounts? + return true unless Rails.configuration.loadbalanced_configuration + return false unless Rails.configuration.allow_user_signup + # No need to retrieve the provider info if the provider is whitelisted + return true if launcher_allow_user_signup_whitelisted?(@user_domain) + # Proceed with retrieving the provider info + provider_info = retrieve_provider_info(@user_domain, 'api2', 'getUserGreenlightCredentials') + provider_info['provider'] == 'greenlight' + end end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index a483a2cd..f0d63d62 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -31,7 +31,7 @@ module SessionsHelper # If email verification is disabled, or the user has verified, go to their room def check_email_verified(user) - if !Rails.configuration.enable_email_verification || user.email_verified + if user.activated? redirect_to user.main_room else redirect_to resend_path @@ -48,24 +48,24 @@ module SessionsHelper @current_user ||= User.find_by(id: session[:user_id]) end - def generate_checksum(customer_name, redirect_url, secret) - string = customer_name + redirect_url + secret + def generate_checksum(user_domain, redirect_url, secret) + string = user_domain + redirect_url + secret OpenSSL::Digest.digest('sha1', string).unpack("H*").first end - def parse_customer_name(hostname) - provider = hostname.split('.') - provider.first == 'www' ? provider.second : provider.first + def parse_user_domain(hostname) + hostname.split('.').first end def omniauth_options(env) gl_redirect_url = (Rails.env.production? ? "https" : env["rack.url_scheme"]) + "://" + env["SERVER_NAME"] + ":" + env["SERVER_PORT"] - env['omniauth.strategy'].options[:customer] = parse_customer_name env["SERVER_NAME"] + user_domain = parse_user_domain(env["SERVER_NAME"]) + env['omniauth.strategy'].options[:customer] = user_domain env['omniauth.strategy'].options[:gl_redirect_url] = gl_redirect_url env['omniauth.strategy'].options[:default_callback_url] = Rails.configuration.gl_callback_url - env['omniauth.strategy'].options[:checksum] = generate_checksum parse_customer_name(env["SERVER_NAME"]), - gl_redirect_url, Rails.configuration.launcher_secret + env['omniauth.strategy'].options[:checksum] = generate_checksum(user_domain, gl_redirect_url, + Rails.configuration.launcher_secret) end def google_omniauth_hd(env, hd) diff --git a/app/models/concerns/api_concern.rb b/app/models/concerns/api_concern.rb index e45ae312..8c46d617 100644 --- a/app/models/concerns/api_concern.rb +++ b/app/models/concerns/api_concern.rb @@ -19,77 +19,6 @@ module APIConcern extend ActiveSupport::Concern - RETURNCODE_SUCCESS = "SUCCESS" - - def bbb_endpoint - Rails.configuration.bigbluebutton_endpoint - end - - def bbb_secret - Rails.configuration.bigbluebutton_secret - end - - # Sets a BigBlueButtonApi object for interacting with the API. - def bbb - @bbb ||= if Rails.configuration.loadbalanced_configuration - if instance_of? Room - # currently in the Room Model - lb_user = retrieve_loadbalanced_credentials(owner.provider) - elsif instance_of? User - # currently in the User Model - lb_user = retrieve_loadbalanced_credentials(provider) - end - BigBlueButton::BigBlueButtonApi.new(remove_slash(lb_user["apiURL"]), lb_user["secret"], "0.8") - else - BigBlueButton::BigBlueButtonApi.new(remove_slash(bbb_endpoint), bbb_secret, "0.8") - end - end - - # Rereives the loadbalanced BigBlueButton credentials for a user. - def retrieve_loadbalanced_credentials(provider) - # Include Omniauth accounts under the Greenlight provider. - provider = "greenlight" if Rails.configuration.providers.include?(provider.to_sym) - - # Build the URI. - uri = encode_bbb_url( - Rails.configuration.loadbalancer_endpoint + "getUser", - Rails.configuration.loadbalancer_secret, - name: provider - ) - - # Make the request. - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') - response = http.get(uri.request_uri) - - unless response.is_a?(Net::HTTPSuccess) - raise "Error retrieving provider credentials: #{response.code} #{response.message}" - end - - # Parse XML. - doc = XmlSimple.xml_in(response.body, 'ForceArray' => false) - - # Return the user credentials if the request succeeded on the loadbalancer. - return doc['user'] if doc['returncode'] == RETURNCODE_SUCCESS - - raise "User with provider #{provider} does not exist." if doc['messageKey'] == "noSuchUser" - raise "API call #{url} failed with #{doc['messageKey']}." - end - - # Builds a request to retrieve credentials from the load balancer. - def encode_bbb_url(base_url, secret, params) - encoded_params = OAuth::Helper.normalize(params) - string = "getUser" + encoded_params + secret - checksum = OpenSSL::Digest.digest('sha1', string).unpack("H*").first - - URI.parse("#{base_url}?#{encoded_params}&checksum=#{checksum}") - end - - # Removes trailing forward slash from a URL. - def remove_slash(s) - s.nil? ? nil : s.chomp("/") - end - # Format recordings to match their current use in the app def format_recordings(api_res) api_res[:recordings].each do |r| diff --git a/app/models/room.rb b/app/models/room.rb index 224e1d64..72c756e4 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -16,8 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License along # with BigBlueButton; if not, see . +require 'bbb_api' + class Room < ApplicationRecord include ::APIConcern + include ::BbbApi before_create :setup diff --git a/app/models/user.rb b/app/models/user.rb index f234f7ff..342f216d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,13 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License along # with BigBlueButton; if not, see . +require 'bbb_api' + class User < ApplicationRecord include ::APIConcern + include ::BbbApi - attr_accessor :reset_token, :activation_token - after_create :create_home_room_if_verified + attr_accessor :reset_token + after_create :initialize_main_room before_save { email.try(:downcase!) } - before_create :create_activation_digest before_destroy :destroy_rooms @@ -125,8 +127,11 @@ class User < ApplicationRecord def activate update_attribute(:email_verified, true) update_attribute(:activated_at, Time.zone.now) + end - initialize_main_room + def activated? + return true unless Rails.configuration.enable_email_verification + email_verified end def send_activation_email(url) @@ -180,7 +185,17 @@ class User < ApplicationRecord end def greenlight_account? - provider == "greenlight" + return provider == "greenlight" unless Rails.configuration.loadbalanced_configuration + # No need to retrive the provider info if the provider is whitelisted + return true if launcher_allow_user_signup_whitelisted?(provider) + # Proceed with fetching the provider info + provider_info = retrieve_provider_info(provider, 'api2', 'getUserGreenlightCredentials') + provider_info['provider'] == 'greenlight' + end + + def activation_token + # Create the token. + create_reset_activation_digest(User.new_token) end def self.digest(string) @@ -195,10 +210,11 @@ class User < ApplicationRecord private - def create_activation_digest - # Create the token and digest. - self.activation_token = User.new_token - self.activation_digest = User.digest(activation_token) + def create_reset_activation_digest(token) + # Create the digest and persist it. + self.activation_digest = User.digest(token) + save + token end # Destory a users rooms when they are removed. @@ -206,16 +222,9 @@ class User < ApplicationRecord rooms.destroy_all end - # Assigns the user a BigBlueButton id and a home room if verified - def create_home_room_if_verified - self.uid = "gl-#{(0...12).map { (65 + rand(26)).chr }.join.downcase}" - - initialize_main_room if email_verified - save - end - # Initializes a room for the user and assign a BigBlueButton user id. def initialize_main_room + self.uid = "gl-#{(0...12).map { (65 + rand(26)).chr }.join.downcase}" self.main_room = Room.create!(owner: self, name: I18n.t("home_room")) save end diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 12526319..ad779d40 100755 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -56,21 +56,21 @@ <% else %> - <% if Rails.configuration.omniauth_bn_launcher %> - <%= link_to t("login"), omniauth_login_url(:bn_launcher), :class => "btn btn-pill btn-outline-primary mx-2" %> - <% elsif Rails.configuration.omniauth_ldap %> - <%= link_to t("login"), omniauth_login_url(:ldap), :class => "btn btn-pill btn-outline-primary mx-2" %> - <% else %> + <% if Rails.configuration.omniauth_ldap %> + <%= link_to t("login"), omniauth_login_url(:ldap), :class => "btn btn-pill btn-outline-primary mx-2" %> + <% elsif allow_greenlight_accounts? %> <%= link_to t("login"), "#loginModal", :class => "btn btn-pill btn-outline-primary mx-2", "data-toggle": "modal" %> + <% else %> + <%= link_to t("login"), omniauth_login_url(:bn_launcher), :class => "btn btn-pill btn-outline-primary mx-2" %> <% end %> - <% if allow_user_signup? %> - <%= link_to t("signup.title"), signup_path, :class => "btn btn-pill btn-outline-primary mx-2" %> - <% end %> + <% if allow_user_signup? && allow_greenlight_accounts? %> + <%= link_to t("signup.title"), signup_path, :class => "btn btn-pill btn-outline-primary mx-2" %> + <% end %> - <%= render "shared/modals/login_modal" %> - <% end %> - - - + <%= render "shared/modals/login_modal" %> + <% end %> + + + diff --git a/app/views/shared/components/_resend_button.html.erb b/app/views/shared/components/_resend_button.html.erb index 0662bb7f..368b347f 100644 --- a/app/views/shared/components/_resend_button.html.erb +++ b/app/views/shared/components/_resend_button.html.erb @@ -14,5 +14,5 @@ %>
- <%= button_to t("verify.resend"), resend_email_path, params: { email_verified: false }, class: "btn btn-primary btn-space" %> + <%= button_to t("verify.resend"), resend_email_path, params: { email: params['email'], email_verified: false }, class: "btn btn-primary btn-space" %>
diff --git a/config/application.rb b/config/application.rb index 7c451837..cd0f3457 100644 --- a/config/application.rb +++ b/config/application.rb @@ -48,22 +48,22 @@ module Greenlight config.gl_callback_url = ENV["GL_CALLBACK_URL"] # Default credentials (test-install.blindsidenetworks.com/bigbluebutton). - config.bigbluebutton_endpoint_default = "http://test-install.blindsidenetworks.com/bigbluebutton/api/" + config.bigbluebutton_endpoint_default = "http://test-install.blindsidenetworks.com/bigbluebutton/" config.bigbluebutton_secret_default = "8cd8ef52e8e101574e400365b55e11a6" - # Setup BigBlueButton configuration. + # Use standalone BigBlueButton server. + config.bigbluebutton_endpoint = ENV["BIGBLUEBUTTON_ENDPOINT"] || config.bigbluebutton_endpoint_default + config.bigbluebutton_secret = ENV["BIGBLUEBUTTON_SECRET"] || config.bigbluebutton_secret_default + + # Fix endpoint format if required. + config.bigbluebutton_endpoint += "api/" unless config.bigbluebutton_endpoint.ends_with?('api/') + if config.loadbalanced_configuration - # Fetch credentials from a loadbalancer based on provider. + # Settings for fetching credentials from a loadbalancer based on provider. config.loadbalancer_endpoint = ENV["LOADBALANCER_ENDPOINT"] config.loadbalancer_secret = ENV["LOADBALANCER_SECRET"] config.launcher_secret = ENV["LAUNCHER_SECRET"] - else - # Use standalone BigBlueButton server. - config.bigbluebutton_endpoint = ENV["BIGBLUEBUTTON_ENDPOINT"] || config.bigbluebutton_endpoint_default - config.bigbluebutton_secret = ENV["BIGBLUEBUTTON_SECRET"] || config.bigbluebutton_secret_default - - # Fix endpoint format if required. - config.bigbluebutton_endpoint += "api/" unless config.bigbluebutton_endpoint.ends_with?('api/') + config.launcher_allow_user_signup = ENV["LAUNCHER_ALLOW_GREENLIGHT_ACCOUNTS"] end # Specify the email address that all mail is sent from diff --git a/config/database.yml b/config/database.yml index 5421b6e0..77296e69 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,9 +10,9 @@ development: test: <<: *default adapter: sqlite3 - database: db/development.sqlite3 + database: db/test.sqlite3 -# Use sqlite in production by default. Greenlight supports +# Use sqlite in production by default. Greenlight supports production: <<: *default adapter: <%= ENV['DB_ADAPTER'] || 'sqlite3' %> diff --git a/config/environments/test.rb b/config/environments/test.rb index 0c28efe9..5f68c35b 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -41,4 +41,12 @@ Rails.application.configure do # Raises error for missing translations # config.action_view.raise_on_missing_translations = true + + # Default credentials (test-install.blindsidenetworks.com/bigbluebutton). + config.bigbluebutton_endpoint_default = 'http://bbb.example.com/bigbluebutton/api/' + config.bigbluebutton_secret_default = 'secret' + + # Use standalone BigBlueButton server. + config.bigbluebutton_endpoint = config.bigbluebutton_endpoint_default + config.bigbluebutton_secret = config.bigbluebutton_secret_default end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb index 6d56e439..1be07628 100644 --- a/config/initializers/application_controller_renderer.rb +++ b/config/initializers/application_controller_renderer.rb @@ -4,7 +4,7 @@ # ActiveSupport::Reloader.to_prepare do # ApplicationController.renderer.defaults.merge!( -# http_host: 'example.org', +# http_host: 'example.com', # https: false # ) # end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index c303e150..e3e54a78 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -1,17 +1,17 @@ # frozen_string_literal: true # List of supported Omniauth providers. -Rails.application.config.providers = [:google, :twitter, :microsoft_office365, :ldap] +Rails.application.config.providers = [] # Set which providers are configured. -Rails.application.config.omniauth_google = ENV['GOOGLE_OAUTH2_ID'].present? && ENV['GOOGLE_OAUTH2_SECRET'].present? -Rails.application.config.omniauth_twitter = ENV['TWITTER_ID'].present? && ENV['TWITTER_SECRET'].present? -Rails.application.config.omniauth_microsoft_office365 = ENV['OFFICE365_KEY'].present? && - ENV['OFFICE365_SECRET'].present? +Rails.application.config.omniauth_bn_launcher = Rails.configuration.loadbalanced_configuration 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_bn_launcher = Rails.configuration.loadbalanced_configuration +Rails.application.config.omniauth_twitter = ENV['TWITTER_ID'].present? && ENV['TWITTER_SECRET'].present? +Rails.application.config.omniauth_google = ENV['GOOGLE_OAUTH2_ID'].present? && ENV['GOOGLE_OAUTH2_SECRET'].present? +Rails.application.config.omniauth_microsoft_office365 = ENV['OFFICE365_KEY'].present? && + ENV['OFFICE365_SECRET'].present? # If LDAP is enabled, override and disable allow_user_signup. Rails.application.config.allow_user_signup = false if Rails.application.config.omniauth_ldap @@ -32,27 +32,39 @@ Rails.application.config.middleware.use OmniAuth::Builder do client_secret: ENV['CLIENT_SECRET'], client_options: { site: ENV['BN_LAUNCHER_REDIRECT_URI'] }, setup: SETUP_PROC + elsif Rails.configuration.omniauth_ldap + Rails.application.config.providers << :ldap + + provider :ldap, + host: ENV['LDAP_SERVER'], + port: ENV['LDAP_PORT'] || '389', + method: ENV['LDAP_METHOD'].blank? ? :plain : ENV['LDAP_METHOD'].to_sym, + allow_username_or_email_login: true, + uid: ENV['LDAP_UID'], + base: ENV['LDAP_BASE'], + bind_dn: ENV['LDAP_BIND_DN'], + password: ENV['LDAP_PASSWORD'] + else + if Rails.configuration.omniauth_twitter + Rails.application.config.providers << :twitter + + provider :twitter, ENV['TWITTER_ID'], ENV['TWITTER_SECRET'] + end + if Rails.configuration.omniauth_google + Rails.application.config.providers << :google + + provider :google_oauth2, ENV['GOOGLE_OAUTH2_ID'], ENV['GOOGLE_OAUTH2_SECRET'], + scope: %w(profile email), + access_type: 'online', + name: 'google', + setup: SETUP_PROC + end + if Rails.configuration.omniauth_microsoft_office365 + Rails.application.config.providers << :microsoft_office365 + + provider :microsoft_office365, ENV['OFFICE365_KEY'], ENV['OFFICE365_SECRET'] + end end - - provider :twitter, ENV['TWITTER_ID'], ENV['TWITTER_SECRET'] - - provider :google_oauth2, ENV['GOOGLE_OAUTH2_ID'], ENV['GOOGLE_OAUTH2_SECRET'], - scope: %w(profile email), - access_type: 'online', - name: 'google', - setup: SETUP_PROC - - provider :microsoft_office365, ENV['OFFICE365_KEY'], ENV['OFFICE365_SECRET'] - - provider :ldap, - host: ENV['LDAP_SERVER'], - port: ENV['LDAP_PORT'] || '389', - method: ENV['LDAP_METHOD'].blank? ? :plain : ENV['LDAP_METHOD'].to_sym, - allow_username_or_email_login: true, - uid: ENV['LDAP_UID'], - base: ENV['LDAP_BASE'], - bind_dn: ENV['LDAP_BIND_DN'], - password: ENV['LDAP_PASSWORD'] end # Redirect back to login in development mode. diff --git a/config/locales/en.yml b/config/locales/en.yml index f10b79c1..7c810bc9 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -77,6 +77,7 @@ en: signout: Sign out home_room: Home Room info_update_success: Information successfully updated. + invalid_user: Login failed due to user not found. Are you sure the email account is correct? invalid_credentials: Login failed due to invalid credentials. Are you sure you entered them correctly? invalid_login_method: Login failed due to account mismatch. You need to log in with omniauth. invite_message: "To invite someone to the meeting, send them this link:" diff --git a/config/routes.rb b/config/routes.rb index 9d38b89d..8c8791b9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -38,7 +38,7 @@ Rails.application.routes.draw do scope '/account_activations' do get '/', to: 'account_activations#show', as: :account_activation get '/edit', to: 'account_activations#edit', as: :edit_account_activation - get '/resend', to: 'account_activations#resend', as: :resend_email + post '/resend', to: 'account_activations#resend', as: :resend_email end # User resources. diff --git a/lib/bbb_api.rb b/lib/bbb_api.rb new file mode 100644 index 00000000..ef16d256 --- /dev/null +++ b/lib/bbb_api.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module BbbApi + RETURNCODE_SUCCESS = "SUCCESS" + + def bbb_endpoint + Rails.configuration.bigbluebutton_endpoint + end + + def bbb_secret + Rails.configuration.bigbluebutton_secret + end + + # Sets a BigBlueButtonApi object for interacting with the API. + def bbb + if Rails.configuration.loadbalanced_configuration + if instance_of? Room + # currently in the Room Model + user_domain = retrieve_provider_info(owner.provider) + elsif instance_of? User + # currently in the User Model + user_domain = retrieve_provider_info(provider) + end + + BigBlueButton::BigBlueButtonApi.new(remove_slash(user_domain["apiURL"]), user_domain["secret"], "0.8") + else + BigBlueButton::BigBlueButtonApi.new(remove_slash(bbb_endpoint), bbb_secret, "0.8") + end + end + + # Rereives info from the loadbalanced in regards to a Provider (or tenant). + def retrieve_provider_info(provider, api = 'api', route = 'getUser') + # Include Omniauth accounts under the Greenlight provider. + provider ||= 'greenlight' + + # Build the URI. + uri = encode_bbb_url( + Rails.configuration.loadbalancer_endpoint + api + '/', + Rails.configuration.loadbalancer_secret, + { name: provider }, + route + ) + + logger.info uri + + # Make the request. + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = (uri.scheme == 'https') + response = http.get(uri.request_uri) + + # Parse XML. + doc = XmlSimple.xml_in(response.body, 'ForceArray' => false) + + raise doc['message'] unless response.is_a?(Net::HTTPSuccess) + + # Return the user credentials if the request succeeded on the loadbalancer. + return doc['user'] if doc['returncode'] == 'SUCCESS' + + raise "User with provider #{provider} does not exist." if doc['messageKey'] == 'noSuchUser' + raise "API call #{url} failed with #{doc['messageKey']}." + end + + # Builds a request to retrieve credentials from the load balancer. + def encode_bbb_url(base_url, secret, params, route = 'getUser') + encoded_params = params.to_param + string = route + encoded_params + secret + checksum = OpenSSL::Digest.digest('sha1', string).unpack('H*').first + + URI.parse("#{base_url}#{route}?#{encoded_params}&checksum=#{checksum}") + end + + # Removes trailing forward slash from a URL. + def remove_slash(s) + s.nil? ? nil : s.chomp("/") + end + + def launcher_allow_user_signup_whitelisted?(provider) + return false unless Rails.configuration.launcher_allow_user_signup + whitelist = Rails.configuration.launcher_allow_user_signup.split(',') + whitelist.include?(provider) + end +end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 2faaa7f1..e9e970c6 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -19,14 +19,10 @@ require "rails_helper" describe SessionsController, type: :controller do - before(:all) do - @user = create(:user, provider: "greenlight", password: "example", password_confirmation: "example") - @omni_user = create(:user, password: "example", password_confirmation: "example") - end - describe "GET #destroy" do before(:each) do - @request.session[:user_id] = @user.id + user = create(:user, provider: "greenlight") + @request.session[:user_id] = user.id get :destroy end @@ -40,22 +36,28 @@ describe SessionsController, type: :controller do end describe "POST #create" do + before { allow(Rails.configuration).to receive(:enable_email_verification).and_return(true) } + before(:each) do + @user1 = create(:user, provider: 'greenlight', password: 'example', password_confirmation: 'example') + @user2 = create(:user, password: 'example', password_confirmation: "example") + end + it "should login user in if credentials valid" do post :create, params: { session: { - email: @user.email, - password: "example", + email: @user1.email, + password: 'example', }, } - expect(@request.session[:user_id]).to eql(@user.id) + expect(@request.session[:user_id]).to eql(@user1.id) end it "should not login user in if credentials invalid" do post :create, params: { session: { - email: @user.email, - password: "invalid", + email: @user1.email, + password: 'invalid', }, } @@ -65,7 +67,7 @@ describe SessionsController, type: :controller do it "should not login user in if account mismatch" do post :create, params: { session: { - email: @omni_user.email, + email: @user2.email, password: "example", }, } @@ -74,18 +76,18 @@ describe SessionsController, type: :controller do end it "should not login user if account is not verified" do - @secondary_user = create(:user, email_verified: false, provider: "greenlight", - password: "example", password_confirmation: "example") + @user3 = create(:user, email_verified: false, provider: "greenlight", + password: "example", password_confirmation: 'example') post :create, params: { session: { - email: @secondary_user.email, - password: "example", + email: @user3.email, + password: 'example', }, } expect(@request.session[:user_id]).to be_nil - expect(response).to redirect_to(account_activation_path(email: @secondary_user.email)) + expect(response).to redirect_to(account_activation_path(email: @user3.email)) end end @@ -99,7 +101,7 @@ describe SessionsController, type: :controller do info: { email: "user@twitter.com", name: "Twitter User", - nickname: "username", + nickname: "twitteruser", image: "example.png", }, ) @@ -108,11 +110,11 @@ describe SessionsController, type: :controller do provider: "bn_launcher", uid: "bn-launcher-user", info: { - email: "user1@google.com", - name: "User1", - nickname: "nick", + email: "user@google.com", + name: "Google User", + nickname: "googleuser", image: "touch.png", - customer: 'ocps', + customer: 'customer1', } ) @@ -121,37 +123,39 @@ describe SessionsController, type: :controller do } end - it "should create and login user with omniauth twitter" do - request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter] - get :omniauth, params: { provider: :twitter } + unless Rails.configuration.omniauth_bn_launcher + it "should create and login user with omniauth twitter" do + request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter] + get :omniauth, params: { provider: :twitter } - u = User.last - expect(u.provider).to eql("twitter") - expect(u.email).to eql("user@twitter.com") - expect(@request.session[:user_id]).to eql(u.id) - end + u = User.last + expect(u.provider).to eql("twitter") + expect(u.email).to eql("user@twitter.com") + expect(@request.session[:user_id]).to eql(u.id) + end - it "should create and login user with omniauth bn launcher" do - request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:bn_launcher] - get :omniauth, params: { provider: 'bn_launcher' } + it "should create and login user with omniauth bn launcher" do + request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:bn_launcher] + get :omniauth, params: { provider: 'bn_launcher' } - u = User.last - expect(u.provider).to eql("ocps") - expect(u.email).to eql("user1@google.com") - expect(@request.session[:user_id]).to eql(u.id) - end + u = User.last + expect(u.provider).to eql("customer1") + expect(u.email).to eql("user@google.com") + expect(@request.session[:user_id]).to eql(u.id) + end - it "should redirect to root on invalid omniauth login" do - request.env["omniauth.auth"] = :invalid_credentials - get :omniauth, params: { provider: :twitter } + it "should redirect to root on invalid omniauth login" do + request.env["omniauth.auth"] = :invalid_credentials + get :omniauth, params: { provider: :twitter } - expect(response).to redirect_to(root_path) - end + expect(response).to redirect_to(root_path) + end - it "should not create session without omniauth env set for google" do - get :omniauth, params: { provider: 'google' } + it "should not create session without omniauth env set for google" do + get :omniauth, params: { provider: 'google' } - expect(response).to redirect_to(root_path) + expect(response).to redirect_to(root_path) + end end it "should not create session without omniauth env set for bn_launcher" do diff --git a/spec/models/room_spec.rb b/spec/models/room_spec.rb index c1f340d1..dfcc714b 100644 --- a/spec/models/room_spec.rb +++ b/spec/models/room_spec.rb @@ -83,7 +83,7 @@ describe Room, type: :model do @room.start_session end.to change { @room.sessions }.by(1) - expect(@room.last_session.utc.to_i).to eq(Time.now.to_i) + expect(@room.last_session).not_to be nil end end @@ -93,13 +93,8 @@ describe Room, type: :model do attendeePW: "testpass" ) - if Rails.configuration.loadbalanced_configuration - endpoint = Rails.configuration.loadbalancer_endpoint - secret = Rails.configuration.loadbalancer_secret - else - endpoint = Rails.configuration.bigbluebutton_endpoint - secret = Rails.configuration.bigbluebutton_secret - end + endpoint = Rails.configuration.bigbluebutton_endpoint + secret = Rails.configuration.bigbluebutton_secret fullname = "fullName=Example" meeting_id = "&meetingID=#{@room.bbb_id}" password = "&password=testpass" diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e5891e6d..72af5432 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -76,30 +76,32 @@ describe User, type: :model do end end - context '#from_omniauth' do - let(:auth) do - { - "uid" => "123456789", - "provider" => "twitter", - "info" => { - "name" => "Test Name", - "nickname" => "username", - "email" => "test@example.com", - "image" => "example.png", - }, - } - end + unless Rails.configuration.omniauth_bn_launcher + context '#from_omniauth' do + let(:auth) do + { + "uid" => "123456789", + "provider" => "twitter", + "info" => { + "name" => "Test Name", + "nickname" => "username", + "email" => "test@example.com", + "image" => "example.png", + }, + } + end - it "should create user from omniauth" do - expect do - user = User.from_omniauth(auth) + it "should create user from omniauth" do + expect do + user = User.from_omniauth(auth) - expect(user.name).to eq("Test Name") - expect(user.email).to eq("test@example.com") - expect(user.image).to eq("example.png") - expect(user.provider).to eq("twitter") - expect(user.social_uid).to eq("123456789") - end.to change { User.count }.by(1) + expect(user.name).to eq("Test Name") + expect(user.email).to eq("test@example.com") + expect(user.image).to eq("example.png") + expect(user.provider).to eq("twitter") + expect(user.social_uid).to eq("123456789") + end.to change { User.count }.by(1) + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e085ae1f..1183220d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -47,7 +47,7 @@ RSpec.configure do |config| # external servers, api stubbing is used to simulate external server # responses config.before(:each) do - stub_request(:any, /#{ENV['BIGBLUEBUTTON_ENDPOINT']}/) + stub_request(:any, /#{"http:\/\/bbb.example.com\/bigbluebutton\/api"}/) .with( headers: { @@ -57,7 +57,7 @@ RSpec.configure do |config| } ) .to_return(status: 200, body: "", headers: {}) - stub_request(:any, /#{ENV['LOADBALANCER_ENDPOINT']}/) + stub_request(:any, /#{ENV['LOADBALANCER_ENDPOINT'] + 'api'}/) .with( headers: { @@ -66,8 +66,8 @@ RSpec.configure do |config| 'User-Agent': 'Ruby', } ) - .to_return(status: 200, body: "", headers: {}) - stub_request(:any, /#{ENV['LOADBALANCER_ENDPOINT'] + 'getUser'}/) + .to_return(status: 200, body: "", headers: {}) if ENV['LOADBALANCER_ENDPOINT'] + stub_request(:any, /#{ENV['LOADBALANCER_ENDPOINT'] + 'api\/getUser'}/) .with( headers: { @@ -83,8 +83,26 @@ RSpec.configure do |config| greenlight 1000 - #{ENV['LOADBALANCER_ENDPOINT']} - #{ENV['LOADBALANCER_SECRET']} + http:\/\/bbb.example.com\/bigbluebutton\/api + secret + + ", headers: {}) if ENV['LOADBALANCER_ENDPOINT'] + stub_request(:any, /#{ENV['LOADBALANCER_ENDPOINT'] + 'api2\/getUserGreenlightCredentials'}/) + .with( + headers: + { + 'Accept': '*/*', + 'Accept-Encoding': 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'User-Agent': 'Ruby', + } + ) + .to_return(status: 200, body: " + + 2.0 + SUCCESS + + greenlight + ", headers: {}) if ENV['LOADBALANCER_ENDPOINT'] end