omniauth-multiprovider 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +13 -18
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/{LICENSE → LICENSE.txt} +0 -0
- data/README.md +52 -7
- data/Rakefile +2 -0
- data/lib/omniauth-multiprovider.rb +3 -1
- data/lib/omniauth/multiprovider.rb +0 -1
- data/lib/omniauth/multiprovider/config.rb +25 -0
- data/lib/omniauth/multiprovider/controllers/callbacks_controller.rb +43 -3
- data/lib/omniauth/multiprovider/error.rb +24 -1
- data/lib/omniauth/multiprovider/models/active_record.rb +14 -0
- data/lib/omniauth/multiprovider/models/authentication.rb +1 -9
- data/lib/omniauth/multiprovider/models/concerns/omni_authenticable.rb +79 -32
- data/lib/omniauth/multiprovider/version.rb +1 -1
- data/lib/omniauth/provider/abstract.rb +6 -38
- data/lib/omniauth/provider/generic.rb +10 -13
- data/omniauth-multiprovider.gemspec +15 -2
- data/spec/omniauth/error_spec.rb +30 -0
- data/spec/omniauth/multiprovider/controllers/callbacks_controller_spec.rb +90 -0
- data/spec/omniauth/multiprovider/models/active_record_spec.rb +21 -0
- data/spec/omniauth/multiprovider/models/concerns/omni_authenticable_spec.rb +67 -0
- data/spec/omniauth/provider/abstract_spec.rb +15 -0
- data/spec/omniauth/provider/facebook_spec.rb +13 -0
- data/spec/omniauth/provider/generic_spec.rb +13 -0
- data/spec/rails_app/.gitignore +17 -0
- data/spec/rails_app/Rakefile +6 -0
- data/spec/rails_app/app/assets/images/.keep +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +16 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +15 -0
- data/spec/rails_app/app/controllers/application_controller.rb +5 -0
- data/spec/rails_app/app/controllers/concerns/.keep +0 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.keep +0 -0
- data/spec/rails_app/app/models/.keep +0 -0
- data/spec/rails_app/app/models/concerns/.keep +0 -0
- data/spec/rails_app/app/models/user.rb +4 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/bin/bundle +3 -0
- data/spec/rails_app/bin/rails +4 -0
- data/spec/rails_app/bin/rake +4 -0
- data/spec/rails_app/bin/setup +29 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +26 -0
- data/spec/rails_app/config/boot.rb +3 -0
- data/spec/rails_app/config/database.yml +25 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/test.rb +42 -0
- data/spec/rails_app/config/initializers/assets.rb +11 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/rails_app/config/initializers/devise.rb +259 -0
- data/spec/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/rails_app/config/initializers/inflections.rb +16 -0
- data/spec/rails_app/config/initializers/mime_types.rb +4 -0
- data/spec/rails_app/config/initializers/session_store.rb +3 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +23 -0
- data/spec/rails_app/config/routes.rb +3 -0
- data/spec/rails_app/config/secrets.yml +22 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/lib/assets/.keep +0 -0
- data/spec/rails_app/lib/tasks/.keep +0 -0
- data/spec/rails_app/public/404.html +67 -0
- data/spec/rails_app/public/422.html +67 -0
- data/spec/rails_app/public/500.html +66 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/shared_examples_for_providers.rb +27 -0
- metadata +226 -7
- data/lib/omniauth/multiprovider/helpers.rb +0 -23
- data/lib/omniauth/provider/guests.rb +0 -12
@@ -1,46 +1,14 @@
|
|
1
1
|
module OmniAuth
|
2
2
|
module Provider
|
3
|
-
class Abstract
|
3
|
+
class Abstract < SimpleDelegator
|
4
4
|
|
5
|
-
def
|
5
|
+
def initialize(controller)
|
6
|
+
super
|
7
|
+
@controller = controller
|
6
8
|
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
def self.find_from_oauth(provider_name, omniauth_data, signed_in_resource=nil)
|
11
|
-
auth = MultiProvider::authentication_klass.normalize(omniauth_data)
|
12
|
-
access_token = auth.credentials.token
|
13
|
-
authentication = MultiProvider.authentication_klass.find_by(provider: provider_name, uid: auth.uid)
|
14
|
-
|
15
|
-
resource = nil
|
16
|
-
|
17
|
-
if authentication
|
18
|
-
if signed_in_resource
|
19
|
-
if authentication.resource == signed_in_resource
|
20
|
-
raise MultiProvider::Error.new 'already connected'
|
21
|
-
else
|
22
|
-
raise MultiProvider::Error.new 'already connected with other user'
|
23
|
-
end
|
24
|
-
end
|
25
|
-
resource = authentication.resource
|
26
|
-
end
|
27
|
-
unless resource
|
28
|
-
if auth.info[:email].blank?
|
29
|
-
auth.info[:email] = MultiProvider::resource_klass.mock_email(provider_name, auth.uid)
|
30
|
-
end
|
31
|
-
email = auth.info[:email]
|
32
|
-
|
33
|
-
raise MultiProvider::Error.new 'email_already_registered' if MultiProvider::resource_klass.find_by(email: email)
|
34
|
-
|
35
|
-
resource = signed_in_resource || MultiProvider::resource_klass.new(
|
36
|
-
email: email,
|
37
|
-
password: Devise.friendly_token[0,20]
|
38
|
-
)
|
39
|
-
|
40
|
-
MultiProvider.authentication_klass.from(auth, resource)
|
41
|
-
|
42
|
-
end
|
43
|
-
resource
|
10
|
+
def handle_request(controller)
|
11
|
+
raise "Should be defined by subclasses"
|
44
12
|
end
|
45
13
|
|
46
14
|
def self.authenticate_from_oauth(provider_name, omniauth_data)
|
@@ -2,21 +2,18 @@ module OmniAuth
|
|
2
2
|
module Provider
|
3
3
|
class Generic < Abstract
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
if resource
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
redirect_to send("new_#{OmniAuth::MultiProvider::resource_mapping}_registration_url")
|
16
|
-
end
|
5
|
+
def handle_request
|
6
|
+
auth = request.env['omniauth.auth']
|
7
|
+
provider_name = auth['provider_name']
|
8
|
+
resource = resource_class.from_oauth auth, signed_in_resource
|
9
|
+
if resource.persisted?
|
10
|
+
sign_in_and_redirect resource, event: :authentication #this will throw if resource is not activated
|
11
|
+
@controller.send(:set_flash_message, :notice, :success, kind: provider_name.to_s.camelize) if is_navigational_format?
|
12
|
+
else
|
13
|
+
session["devise.#{provider_name}_data"] = auth.except('extra')
|
14
|
+
redirect_to send("new_#{OmniAuth::MultiProvider::resource_mapping}_registration_url")
|
17
15
|
end
|
18
16
|
end
|
19
|
-
|
20
17
|
end
|
21
18
|
end
|
22
19
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
2
5
|
require 'omniauth/multiprovider/version'
|
3
6
|
|
4
7
|
Gem::Specification.new do |s|
|
@@ -8,12 +11,22 @@ Gem::Specification.new do |s|
|
|
8
11
|
s.description = "Provides a simple approach to support many oauth providers to devise"
|
9
12
|
s.authors = ["German Del Zotto"]
|
10
13
|
s.email = 'germ@ndz.com.ar'
|
11
|
-
s.files = `git ls-files`.split("\
|
14
|
+
s.files = `git ls-files -z`.split("\x0")
|
15
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
12
16
|
s.require_paths = ['lib']
|
13
17
|
s.homepage = 'https://github.com/1uptalent/omniauth-multiprovider'
|
14
18
|
s.license = 'MIT'
|
15
19
|
|
16
20
|
s.add_dependency('devise', '~> 3.2')
|
21
|
+
s.add_dependency('activesupport', ">= 3.2.6", "< 5")
|
22
|
+
s.add_dependency('activerecord', ">= 3.2.6", "< 5")
|
17
23
|
s.add_dependency('omniauth-oauth2', '~> 1.1')
|
18
24
|
s.add_dependency('hashugar', '~> 0.0', '>= 0.0.6')
|
25
|
+
|
26
|
+
s.add_development_dependency "bundler", "~> 1.7"
|
27
|
+
s.add_development_dependency "rake", "~> 10.0"
|
28
|
+
|
29
|
+
s.add_development_dependency "rspec", "~> 3.1"
|
30
|
+
s.add_development_dependency "rspec-rails", "~> 3.1"
|
31
|
+
s.add_development_dependency "sqlite3"
|
19
32
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::MultiProvider::Error do
|
4
|
+
it 'is a runtime error' do
|
5
|
+
expect { raise OmniAuth::MultiProvider::Error }.to raise_error RuntimeError
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe OmniAuth::MultiProvider::AlreadyBoundError do
|
10
|
+
let(:current_resource) { double('current_resource') }
|
11
|
+
let(:bound_resource) { double("bound_resource") }
|
12
|
+
|
13
|
+
it 'provides the current and bound resources' do
|
14
|
+
error = OmniAuth::MultiProvider::AlreadyBoundError.new(current_resource, bound_resource)
|
15
|
+
expect(error.bound_to).to be bound_resource
|
16
|
+
expect(error.current).to be current_resource
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#message' do
|
20
|
+
it 'is "bound_to_same" for the same current and bound resource' do
|
21
|
+
error = OmniAuth::MultiProvider::AlreadyBoundError.new(current_resource, current_resource)
|
22
|
+
expect(error.message).to eq 'bound_to_same'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is "bound_to_other" for different current and bound resources' do
|
26
|
+
error = OmniAuth::MultiProvider::AlreadyBoundError.new(current_resource, bound_resource)
|
27
|
+
expect(error.message).to eq 'bound_to_other'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'OmniAuth::MultiProvider::CallbacksController' do
|
4
|
+
let(:klass) { OmniAuth::MultiProvider::CallbacksController }
|
5
|
+
|
6
|
+
it 'defines methods for omniauth providers' do
|
7
|
+
Devise.omniauth :foo
|
8
|
+
Devise.omniauth :bar
|
9
|
+
methods = klass.action_methods
|
10
|
+
expect(methods).to include 'foo'
|
11
|
+
expect(methods).to include 'bar'
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#create_handler' do
|
15
|
+
it 'creates a method with the handler name' do
|
16
|
+
klass.send :create_handler, 'whatever'
|
17
|
+
expect(klass.action_methods).to include 'whatever'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates a generic handler for unknown providers' do
|
21
|
+
expect_any_instance_of(OmniAuth::Provider::Generic).to receive :handle_request
|
22
|
+
klass.send :create_handler, 'whatever'
|
23
|
+
klass.new.whatever
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'creates a custom handler for Facebook' do
|
27
|
+
expect_any_instance_of(OmniAuth::Provider::Facebook).to receive :handle_request
|
28
|
+
klass.send :create_handler, 'facebook'
|
29
|
+
klass.new.facebook
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#handle_request' do
|
34
|
+
subject { klass.new }
|
35
|
+
let(:request) { ActionDispatch::TestRequest.new }
|
36
|
+
let(:response) { ActionDispatch::TestResponse.new }
|
37
|
+
before { klass.send :public, :handle_request }
|
38
|
+
before { allow(subject).to receive(:devise_mapping) { Devise.mappings[:user]} }
|
39
|
+
before do
|
40
|
+
subject.instance_variable_set '@_request', request
|
41
|
+
subject.instance_variable_set '@_response', response
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when it raises' do
|
45
|
+
let(:provider) { double('a provider') }
|
46
|
+
|
47
|
+
context 'MultiProvider errors' do
|
48
|
+
let(:error) { OmniAuth::MultiProvider::Error.new 'message_key' }
|
49
|
+
before { expect(provider).to receive(:handle_request).and_raise(error) }
|
50
|
+
|
51
|
+
context 'default behavior' do
|
52
|
+
it 'does not raise' do
|
53
|
+
expect { subject.handle_request provider }.not_to raise_error
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'sets the flash[:alert] with an I18N error message' do
|
57
|
+
subject.handle_request provider
|
58
|
+
expect(subject.flash[:alert]).to match %r{en.devise.callbacks.user.#{error.message}}
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'redirects to the remembered url' do
|
62
|
+
location = '/foo/bar/baz'
|
63
|
+
subject.store_location_for :user, location
|
64
|
+
subject.handle_request provider
|
65
|
+
expect(response.location).to eq "http://test.host#{location}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does raise if the error is not handled' do
|
70
|
+
allow(subject).to receive(:handle_provider_error) { false }
|
71
|
+
expect { subject.handle_request provider }.to raise_error error
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'unexpected errors' do
|
76
|
+
let(:unexpected_error) { Class.new(RuntimeError).new("some message") }
|
77
|
+
before { expect(provider).to receive(:handle_request).and_raise(unexpected_error) }
|
78
|
+
it 're-raises the error by default' do
|
79
|
+
expect { subject.handle_request provider }.to raise_error unexpected_error
|
80
|
+
end
|
81
|
+
it 'does not raise if the error is handled' do
|
82
|
+
allow(subject).to receive(:handle_unexpected_error) { true }
|
83
|
+
expect { subject.handle_request provider }.not_to raise_error
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::MultiProvider::ActiveRecord do
|
4
|
+
before do
|
5
|
+
ActiveRecord::Base.connection.create_table :cs, temporary: true
|
6
|
+
end
|
7
|
+
after do
|
8
|
+
ActiveRecord::Base.connection.drop_table :cs
|
9
|
+
end
|
10
|
+
let!(:klass) do
|
11
|
+
class C < ActiveRecord::Base
|
12
|
+
omniauthenticable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can create instances' do
|
17
|
+
klass.new
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::MultiProvider::OmniAuthenticable do
|
4
|
+
describe '#from_oauth' do
|
5
|
+
context 'already authenticated' do
|
6
|
+
let(:user) do
|
7
|
+
u = User.new
|
8
|
+
u.save validate: false
|
9
|
+
u
|
10
|
+
end
|
11
|
+
let(:authentication) do
|
12
|
+
Authentication.create(resource: user, provider: 'test', uid: '123')
|
13
|
+
end
|
14
|
+
let(:omniauth_data) do
|
15
|
+
#https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
|
16
|
+
{
|
17
|
+
provider: authentication.provider,
|
18
|
+
uid: authentication.uid,
|
19
|
+
credentials: { token: 'foo' }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns the existing user' do
|
24
|
+
expected = user
|
25
|
+
actual = User.from_oauth omniauth_data
|
26
|
+
expect(expected).to eq actual
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'fails if the current user is different' do
|
30
|
+
another_user = User.new
|
31
|
+
expect do
|
32
|
+
User.from_oauth omniauth_data, another_user
|
33
|
+
end.to raise_error OmniAuth::MultiProvider::AlreadyBoundError
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'succeeds if already bound to current user' do
|
37
|
+
expect do
|
38
|
+
User.from_oauth omniauth_data, user
|
39
|
+
end.not_to raise_error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#oauth_to_attributes' do
|
45
|
+
it 'returns email, password and password_confirmation' do
|
46
|
+
oauth = {provider: 'foo', info: { email: 'some_email' } }.to_hashugar
|
47
|
+
attrs = User.oauth_to_attributes oauth
|
48
|
+
expect(attrs.keys).to match [:email, :password, :password_confirmation]
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns a mock email when oauth data does not have it' do
|
52
|
+
oauth = {provider: 'foo', info: {} }.to_hashugar
|
53
|
+
email = User.oauth_to_attributes(oauth)[:email]
|
54
|
+
expect(email).to match /@from-foo\.example$/
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns the oauth email if present' do
|
58
|
+
oauth = {provider: 'foo', info: {email: 'smith@example.com'} }.to_hashugar
|
59
|
+
email = User.oauth_to_attributes(oauth)[:email]
|
60
|
+
expect(email).to eq 'smith@example.com'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#update_from_oauth' do
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/shared_examples_for_providers'
|
3
|
+
|
4
|
+
describe OmniAuth::Provider::Abstract do
|
5
|
+
let(:controller) { double('a controller') }
|
6
|
+
subject { described_class.new controller }
|
7
|
+
|
8
|
+
it_behaves_like 'a provider'
|
9
|
+
|
10
|
+
describe '#handle_request' do
|
11
|
+
it 'raises since it is a template method' do
|
12
|
+
expect{subject.handle_request double('controller')}.to raise_error "Should be defined by subclasses"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/shared_examples_for_providers'
|
3
|
+
|
4
|
+
describe OmniAuth::Provider::Facebook do
|
5
|
+
let(:controller) { double('a controller') }
|
6
|
+
subject { described_class.new controller }
|
7
|
+
|
8
|
+
it_behaves_like 'a provider'
|
9
|
+
|
10
|
+
describe '#handle_request' do
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/shared_examples_for_providers'
|
3
|
+
|
4
|
+
describe OmniAuth::Provider::Generic do
|
5
|
+
let(:controller) { double('a controller') }
|
6
|
+
subject { described_class.new controller }
|
7
|
+
|
8
|
+
it_behaves_like 'a provider'
|
9
|
+
|
10
|
+
describe '#handle_request' do
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore the default SQLite database.
|
11
|
+
/db/*.sqlite3
|
12
|
+
/db/*.sqlite3-journal
|
13
|
+
|
14
|
+
# Ignore all logfiles and tempfiles.
|
15
|
+
/log/*
|
16
|
+
!/log/.keep
|
17
|
+
/tmp
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require turbolinks
|
16
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|