bookingsync-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +31 -0
  4. data/app/controllers/sessions_controller.rb +18 -0
  5. data/app/views/sessions/failure.html.erb +5 -0
  6. data/config/routes.rb +5 -0
  7. data/lib/bookingsync.rb +4 -0
  8. data/lib/bookingsync/engine.rb +88 -0
  9. data/lib/bookingsync/engine/auth_helpers.rb +103 -0
  10. data/lib/bookingsync/engine/helpers.rb +15 -0
  11. data/lib/bookingsync/engine/model.rb +59 -0
  12. data/lib/bookingsync/engine/session_helpers.rb +18 -0
  13. data/lib/bookingsync/engine/token_helpers.rb +31 -0
  14. data/lib/bookingsync/engine/version.rb +3 -0
  15. data/lib/generators/bookingsync/install/install_generator.rb +16 -0
  16. data/lib/generators/bookingsync/install/templates/create_bookingsync_accounts.rb +14 -0
  17. data/spec/controllers/sessions_controller_spec.rb +57 -0
  18. data/spec/dummy/README.rdoc +28 -0
  19. data/spec/dummy/Rakefile +6 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  23. data/spec/dummy/app/controllers/authenticated_controller.rb +6 -0
  24. data/spec/dummy/app/controllers/home_controller.rb +4 -0
  25. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  26. data/spec/dummy/app/models/account.rb +3 -0
  27. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  28. data/spec/dummy/bin/bundle +3 -0
  29. data/spec/dummy/bin/rails +4 -0
  30. data/spec/dummy/bin/rake +4 -0
  31. data/spec/dummy/config.ru +4 -0
  32. data/spec/dummy/config/application.rb +23 -0
  33. data/spec/dummy/config/boot.rb +5 -0
  34. data/spec/dummy/config/database.yml +17 -0
  35. data/spec/dummy/config/environment.rb +5 -0
  36. data/spec/dummy/config/environments/development.rb +29 -0
  37. data/spec/dummy/config/environments/production.rb +80 -0
  38. data/spec/dummy/config/environments/test.rb +36 -0
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  41. data/spec/dummy/config/initializers/inflections.rb +16 -0
  42. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  43. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  44. data/spec/dummy/config/initializers/session_store.rb +3 -0
  45. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  46. data/spec/dummy/config/locales/en.yml +23 -0
  47. data/spec/dummy/config/routes.rb +4 -0
  48. data/spec/dummy/db/development.sqlite3 +0 -0
  49. data/spec/dummy/db/migrate/20140522110326_create_accounts.rb +8 -0
  50. data/spec/dummy/db/migrate/20140522110454_add_o_auth_fields_to_accounts.rb +11 -0
  51. data/spec/dummy/db/schema.rb +29 -0
  52. data/spec/dummy/db/test.sqlite3 +0 -0
  53. data/spec/dummy/log/development.log +47 -0
  54. data/spec/dummy/log/newrelic_agent.log +21 -0
  55. data/spec/dummy/log/test.log +3080 -0
  56. data/spec/dummy/public/404.html +58 -0
  57. data/spec/dummy/public/422.html +58 -0
  58. data/spec/dummy/public/500.html +57 -0
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/fixtures/accounts.yml +17 -0
  61. data/spec/models/account_spec.rb +54 -0
  62. data/spec/spec_helper.rb +15 -0
  63. data/spec/support/omniauth.rb +13 -0
  64. metadata +237 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 148999512eaa0db2fb982dd0d359fb5650f48f44
4
+ data.tar.gz: 821ee7611331e250f0c0d4f2a9f6bc4334f8bec2
5
+ SHA512:
6
+ metadata.gz: e6c2439872589f7bac7801dd61b28e13c4f33e7e5c57e516a3c40f90e2a593e8c22d07ac59bed9d489d3a09b7b7d295593005f6fdcffd3a9c4f39b45964e8391
7
+ data.tar.gz: 9cdd1bf10710164feedf35b0fb9a0ffea02109b87be78e049fd243be7025c510a54e2ea20c98fb76244a07dfefae1391da77f6f75fae9ef74e9c4a0c94c8efbd
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+ require 'thor'
9
+
10
+ RDoc::Task.new(:rdoc) do |rdoc|
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = 'BookingSync'
13
+ rdoc.options << '--line-numbers'
14
+ rdoc.rdoc_files.include('README.rdoc')
15
+ rdoc.rdoc_files.include('lib/**/*.rb')
16
+ end
17
+
18
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
19
+ load 'rails/tasks/engine.rake'
20
+
21
+
22
+
23
+ Bundler::GemHelper.install_tasks
24
+
25
+ require 'rspec/core'
26
+ require 'rspec/core/rake_task'
27
+
28
+ desc "Run all specs in spec directory (excluding plugin specs)"
29
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
30
+
31
+ task default: :spec
@@ -0,0 +1,18 @@
1
+ class SessionsController < ApplicationController
2
+ def create
3
+ auth = request.env["omniauth.auth"]
4
+ account = ::Account.from_omniauth(auth)
5
+ account_authorized(account)
6
+ redirect_to after_bookingsync_sign_in_path
7
+ end
8
+
9
+ def destroy
10
+ clear_authorization!
11
+ redirect_to after_bookingsync_sign_out_path
12
+ end
13
+
14
+ def failure
15
+ allow_bookingsync_iframe
16
+ @error_message = params[:message].try(:humanize)
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ <h1>Authentication failed</h1>
2
+
3
+ <p>Error: <%= @error_message %></p>
4
+
5
+ <a href="/auth/bookingsync">Sign in again</a>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do
2
+ get '/auth/:provider/callback', to: 'sessions#create'
3
+ get '/auth/failure', to: 'sessions#failure'
4
+ get '/signout' => 'sessions#destroy', as: :signout
5
+ end
@@ -0,0 +1,4 @@
1
+ require "bookingsync/engine"
2
+
3
+ module BookingSync
4
+ end
@@ -0,0 +1,88 @@
1
+ require 'omniauth'
2
+ require 'omniauth-bookingsync'
3
+ require 'bookingsync-api'
4
+
5
+ module BookingSync
6
+ class Engine < ::Rails::Engine
7
+ initializer "bookingsync.add_omniauth" do |app|
8
+ app.middleware.use OmniAuth::Builder do
9
+ provider :bookingsync,
10
+ ENV['BOOKINGSYNC_APP_ID'],
11
+ ENV['BOOKINGSYNC_APP_SECRET'],
12
+ scope: ENV['BOOKINGSYNC_SCOPE'],
13
+ setup: -> (env) {
14
+ if url = ENV['BOOKINGSYNC_URL']
15
+ env['omniauth.strategy'].options[:client_options].site = url
16
+ end
17
+ env['omniauth.strategy'].options[:client_options].ssl = {
18
+ verify: ENV['BOOKINGSYNC_VERIFY_SSL'] != 'false'
19
+ }
20
+ }
21
+ end
22
+ end
23
+
24
+ initializer "bookingsync.controller_helper" do |app|
25
+ require "bookingsync/engine/helpers"
26
+ require "bookingsync/engine/session_helpers"
27
+ require "bookingsync/engine/auth_helpers"
28
+ require "bookingsync/engine/token_helpers"
29
+
30
+ ActiveSupport.on_load :action_controller do
31
+ include BookingSync::Engine::Helpers
32
+ include BookingSync::Engine::SessionHelpers
33
+ include BookingSync::Engine::AuthHelpers
34
+ include BookingSync::Engine::TokenHelpers
35
+ end
36
+ end
37
+
38
+ # @return [Boolean]
39
+ cattr_accessor :embedded
40
+ self.embedded = true
41
+
42
+ # Duration of inactivity after which the authorization will be reset.
43
+ # See {BookingSync::Engine::SessionHelpers#sign_out_if_inactive}.
44
+ # @return [Fixnum]
45
+ cattr_accessor :sign_out_after
46
+ self.sign_out_after = 10.minutes
47
+
48
+ # Set the engine into embedded mode.
49
+ #
50
+ # Embedded apps are loaded from within the BookingSync app store, and
51
+ # have a different method to redirect during the OAuth authorization
52
+ # process. <b>This method will not work when the app is not embedded</b>.
53
+ def self.embedded!
54
+ self.embedded = true
55
+ end
56
+
57
+ # Set the engine into standalone mode.
58
+ #
59
+ # This setting is requried for the applications using this Engine
60
+ # to work outside of the BookingSync app store.
61
+ #
62
+ # Restores the normal mode of redirecting during OAuth authorization.
63
+ def self.standalone!
64
+ self.embedded = false
65
+ end
66
+
67
+ # OAuth client configured for the application.
68
+ #
69
+ # The ENV variables used for configuration are described in {file:README.md}.
70
+ #
71
+ # @return [OAuth2::Client] configured OAuth client
72
+ def self.oauth_client
73
+ client_options = {
74
+ site: ENV['BOOKINGSYNC_URL'] || 'https://www.bookingsync.com',
75
+ connection_opts: { headers: { accept: "application/vnd.api+json" } }
76
+ }
77
+ client_options[:ssl] = { verify: ENV['BOOKINGSYNC_VERIFY_SSL'] != 'false' }
78
+ OAuth2::Client.new(ENV['BOOKINGSYNC_APP_ID'], ENV['BOOKINGSYNC_APP_SECRET'],
79
+ client_options)
80
+ end
81
+
82
+ def self.application_token
83
+ oauth_client.client_credentials.get_token
84
+ end
85
+ end
86
+ end
87
+
88
+ require "bookingsync/engine/model"
@@ -0,0 +1,103 @@
1
+ module BookingSync::Engine::AuthHelpers
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ rescue_from OAuth2::Error, with: :handle_oauth_error
6
+ rescue_from BookingSync::API::Unauthorized, with: :reset_authorization!
7
+ helper_method :current_account
8
+ end
9
+
10
+ private
11
+
12
+ # @return [Account, nil] currently authorized Account or nil if unauthorized
13
+ def current_account
14
+ @current_account ||= ::Account.find_by(uid: session[:account_id]) if session[:account_id].present?
15
+ end
16
+
17
+ # Callback after account is authorized.
18
+ #
19
+ # Stores the authorized account's uid in the session.
20
+ #
21
+ # @param account [Account] the just authorized account
22
+ def account_authorized(account)
23
+ session[:account_id] = account.uid.to_s
24
+ end
25
+
26
+ # Clear authorization if the account passed from the BookingSync app store
27
+ # embed doesn't match the currently authorized account
28
+ def enforce_requested_account_authorized!
29
+ clear_authorization! unless requested_account_authorized?
30
+ end
31
+
32
+ # Checks if the account requested from the BookingSync app store embed
33
+ # matches currently authorized account.
34
+ def requested_account_authorized?
35
+ session[:_bookingsync_account_id].blank? ||
36
+ session[:_bookingsync_account_id] == session[:account_id]
37
+ end
38
+
39
+ # Removes the authorization from session. Will not redirect to any other
40
+ # page, see {#reset_authorization!}
41
+ def clear_authorization!
42
+ session[:account_id] = nil
43
+ end
44
+
45
+ # Removes authorization from session and requests new authorization.
46
+ # For removing authorization without redirecting, see {#clear_authorization!}.
47
+ def reset_authorization!
48
+ session[:_bookingsync_account_id] =
49
+ params[:account_id].presence || session[:account_id]
50
+ clear_authorization!
51
+ request_authorization!
52
+ end
53
+
54
+ def request_authorization!
55
+ if BookingSync::Engine.embedded
56
+ allow_bookingsync_iframe
57
+ path = "/auth/bookingsync/?account_id=#{session[:_bookingsync_account_id]}"
58
+ render text: "<script type='text/javascript'>top.location.href = '#{path}';</script>"
59
+ else
60
+ redirect_to "/auth/bookingsync"
61
+ end
62
+ end
63
+
64
+ # Handler to rescue OAuth errors
65
+ #
66
+ # @param error [OAuth2::Error] the rescued error
67
+ def handle_oauth_error(error)
68
+ if error.code == "Not authorized"
69
+ current_account.try(:clear_token!)
70
+ reset_authorization!
71
+ else
72
+ raise
73
+ end
74
+ end
75
+
76
+ # Path to which the user should be redirected after successful authorization.
77
+ # This method should be overridden in applications using this engine.
78
+ #
79
+ # Defaults to root_path.
80
+ def after_bookingsync_sign_in_path
81
+ root_path
82
+ end
83
+
84
+ # Path to which the user should be redirected after sign out.
85
+ # This method should be overridden in applications using this engine.
86
+ #
87
+ # Defaults to root_path.
88
+ def after_bookingsync_sign_out_path
89
+ root_path
90
+ end
91
+
92
+ # Requests authorization if not currently authorized.
93
+ def authenticate_account!
94
+ store_bookingsync_account_id if BookingSync::Engine.embedded
95
+ sign_out_if_inactive
96
+ enforce_requested_account_authorized!
97
+ request_authorization! unless current_account
98
+ end
99
+
100
+ def store_bookingsync_account_id # :nodoc:
101
+ session[:_bookingsync_account_id] = params.delete(:_bookingsync_account_id)
102
+ end
103
+ end
@@ -0,0 +1,15 @@
1
+ # General helpers related to integrating applications with BookingSync
2
+ module BookingSync::Engine::Helpers
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ # Clears the X-Frame-Options header so that the application can be embedded
8
+ # in an iframe. This is required for loading applications in the
9
+ # BookingSync app store.
10
+ #
11
+ # This should set ALLOW-FROM, but it's not supported in Chrome and Safari.
12
+ def allow_bookingsync_iframe
13
+ response.headers['X-Frame-Options'] = '' if ::BookingSync::Engine.embedded
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ module BookingSync::Engine::Model
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def from_omniauth(auth)
6
+ where(auth.slice(:provider, :uid)).first_or_initialize.tap do |account|
7
+ account.provider = auth.provider
8
+ account.uid = auth.uid
9
+ account.name = auth.info.business_name
10
+
11
+ account.update_token(auth.credentials)
12
+
13
+ account.save!
14
+ end
15
+ end
16
+ end
17
+
18
+ def token
19
+ @token ||= begin
20
+ token_options = {}
21
+ if oauth_refresh_token
22
+ token_options[:refresh_token] = oauth_refresh_token
23
+ token_options[:expires_at] = oauth_expires_at
24
+ end
25
+
26
+ token = OAuth2::AccessToken.new(BookingSync::Engine.oauth_client,
27
+ oauth_access_token, token_options)
28
+
29
+ if token.expired?
30
+ token = token.refresh!
31
+ update_token!(token)
32
+ end
33
+
34
+ token
35
+ end
36
+ end
37
+
38
+ def api
39
+ @api ||= BookingSync::API::Client.new(token.token)
40
+ end
41
+
42
+ def update_token(token)
43
+ self.oauth_access_token = token.token
44
+ self.oauth_refresh_token = token.refresh_token
45
+ self.oauth_expires_at = token.expires_at
46
+ end
47
+
48
+ def update_token!(token)
49
+ update_token(token)
50
+ save!
51
+ end
52
+
53
+ def clear_token!
54
+ self.oauth_access_token = nil
55
+ self.oauth_refresh_token = nil
56
+ self.oauth_expires_at = nil
57
+ save!
58
+ end
59
+ end
@@ -0,0 +1,18 @@
1
+ module BookingSync::Engine::SessionHelpers
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ # Automatically resets authorization when the session goes inactive.
7
+ # This is only enabled when the engine is set to embedded mode.
8
+ def sign_out_if_inactive
9
+ return unless BookingSync::Engine.embedded
10
+
11
+ last_visit = session[:_bookingsync_last_visit]
12
+ session[:_bookingsync_last_visit] = Time.now.to_i
13
+
14
+ if last_visit && (Time.now.to_i - last_visit > BookingSync::Engine.sign_out_after)
15
+ clear_authorization!
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ module BookingSync::Engine::TokenHelpers
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ # OAuth access token for the current account. Will refresh the token
7
+ # if it's expired and store the new token in the database.
8
+ #
9
+ # @return [OAuth2::AccessToken] access token for current account
10
+ def current_account_token
11
+ current_account.token
12
+ end
13
+
14
+ # OAuth access token for the application. The token is obtained from
15
+ # {BookingSync::Engine#appliation_token}.
16
+ #
17
+ # Will fetch new token if the current one is expired.
18
+ #
19
+ # The token is stored in thread local storage, to reduce the amount of
20
+ # token requests.
21
+ #
22
+ # @return [OAuth2::AccessToken] access token for application
23
+ def application_token
24
+ token = Thread.current[:_bookingsync_application_token]
25
+ if token.nil? || token.expired?
26
+ token = Thread.current[:_bookingsync_application_token] =
27
+ BookingSync::Engine.application_token
28
+ end
29
+ token
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module BookingSync
2
+ ENGINE_VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module BookingSync
5
+ module Generators
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ source_root File.join(File.dirname(__FILE__), 'templates')
10
+
11
+ def copy_migrations
12
+ migration_template "create_accounts.rb", "db/migrate/create_accounts.rb"
13
+ end
14
+ end
15
+ end
16
+ end