From 0217d90a9f1c45acc1c064241ce9cddf72f6e18d Mon Sep 17 00:00:00 2001 From: Nicolae Claudius Date: Fri, 3 Jan 2014 22:55:23 +0200 Subject: [PATCH] google sign up / sign in integration --- Gemfile | 3 + Gemfile.lock | 21 +++++ app/assets/stylesheets/application.css.scss | 2 + app/assets/stylesheets/pages/home.css.scss | 33 +++++--- .../stylesheets/theme/overrides.css.scss | 6 ++ .../users/authentications_controller.rb | 6 ++ .../users/omniauth_callbacks_controller.rb | 78 +++++++++++++++++++ app/helpers/users/authentications_helper.rb | 15 ++++ app/models/authentication.rb | 3 + app/models/user.rb | 5 +- app/models/user_ability.rb | 3 + app/views/public/pages/home.html.erb | 5 +- app/views/users/confirmations/new.html.erb | 30 ++++--- app/views/users/passwords/new.html.erb | 30 ++++--- app/views/users/registrations/_new.html.erb | 12 ++- app/views/users/registrations/edit.html.erb | 8 +- app/views/users/registrations/new.html.erb | 11 ++- app/views/users/sessions/new.html.erb | 55 ++++++++----- app/views/users/shared/_links.erb | 8 +- app/views/users/unlocks/new.html.erb | 30 ++++--- config/initializers/devise.rb | 8 ++ config/routes.rb | 9 ++- config/settings.sample.yml | 2 + .../20140102165905_create_authentications.rb | 14 ++++ db/schema.rb | 15 +++- .../users/authentications_routing_spec.rb | 15 ++++ .../assets/stylesheets/bootstrap-social.css | 57 ++++++++++++++ 27 files changed, 402 insertions(+), 82 deletions(-) create mode 100644 app/controllers/users/authentications_controller.rb create mode 100644 app/controllers/users/omniauth_callbacks_controller.rb create mode 100644 app/helpers/users/authentications_helper.rb create mode 100644 app/models/authentication.rb create mode 100644 db/migrate/20140102165905_create_authentications.rb create mode 100644 spec/routing/users/authentications_routing_spec.rb create mode 100644 vendor/assets/stylesheets/bootstrap-social.css diff --git a/Gemfile b/Gemfile index 4cdabcf..24b8f92 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ source 'http://rubygems.org' gem 'rails', '4.0.2' gem 'mysql2', '~> 0.3.13' gem 'devise', '~> 3.2.2' +gem 'omniauth', '~> 1.1.4' +gem 'omniauth-google-apps', '~> 0.1.0' gem 'cancan', '= 1.6.7' gem 'squeel', '~> 1.1.0' gem 'sentient_model', '~> 1.0.4' @@ -24,6 +26,7 @@ gem 'unicorn', '~> 4.6.3' gem 'sass-rails', '~> 4.0.0' gem 'compass-rails', '~> 1.1.0' gem 'bootstrap-sass', '~> 3.0.3.0' +gem 'font-awesome-rails', '~> 4.0.3.0' gem 'webshims-rails', '~> 1.11.1' gem 'detect_timezone_rails', '~> 0.0.3' gem 'nprogress-rails', '~> 0.1.2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 7e4176b..c1b247c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -127,6 +127,7 @@ GEM fssm (0.2.10) haml (4.0.4) tilt + hashie (2.0.5) hike (1.2.3) i18n (0.6.9) jquery-rails (3.0.4) @@ -164,6 +165,17 @@ GEM nprogress-rails (0.1.2.3) oily_png (1.1.0) chunky_png (~> 1.2.7) + omniauth (1.1.4) + hashie (>= 1.2, < 3) + rack + omniauth-google-apps (0.1.0) + omniauth (~> 1.0) + omniauth-openid (~> 1.0) + ruby-openid (~> 2.3.0) + ruby-openid-apps-discovery (~> 1.2.0) + omniauth-openid (1.0.1) + omniauth (~> 1.0) + rack-openid (~> 1.3.1) orm_adapter (0.5.0) polyamorous (0.6.4) activerecord (>= 3.0) @@ -171,6 +183,9 @@ GEM quiet_assets (1.0.2) railties (>= 3.1, < 5.0) rack (1.5.2) + rack-openid (1.3.1) + rack (>= 1.1.0) + ruby-openid (>= 2.1.8) rack-pjax (0.7.0) nokogiri (~> 1.5) rack (~> 1.3) @@ -223,6 +238,9 @@ GEM rspec-core (~> 2.14.0) rspec-expectations (~> 2.14.0) rspec-mocks (~> 2.14.0) + ruby-openid (2.3.0) + ruby-openid-apps-discovery (1.2.0) + ruby-openid (>= 2.1.7) safe_yaml (0.9.7) sass (3.2.13) sass-rails (4.0.1) @@ -301,6 +319,7 @@ DEPENDENCIES devise (~> 3.2.2) factory_girl_rails (~> 4.3.0) faker (~> 1.2.0) + font-awesome-rails (~> 4.0.3.0) jquery-rails (~> 3.0.4) jquery-ui-rails (~> 4.1.0) json (~> 1.8.0) @@ -311,6 +330,8 @@ DEPENDENCIES nilify_blanks (~> 1.0.2) nprogress-rails (~> 0.1.2.2) oily_png (~> 1.1.0) + omniauth (~> 1.1.4) + omniauth-google-apps (~> 0.1.0) quiet_assets (~> 1.0.1) rails (= 4.0.2) rails-settings-cached (~> 0.3.1) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 408c0f6..a5b1dd6 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -5,6 +5,8 @@ *= require nprogress *= require animate +*= require bootstrap-social +*= require font-awesome *= require_self */ diff --git a/app/assets/stylesheets/pages/home.css.scss b/app/assets/stylesheets/pages/home.css.scss index 56b8952..b315c2a 100644 --- a/app/assets/stylesheets/pages/home.css.scss +++ b/app/assets/stylesheets/pages/home.css.scss @@ -1,15 +1,6 @@ @import "compass/css3/images"; @import "compass/css3/border-radius"; -.home-sign-up { - h3 { - margin-top: 0; - } - .actions { - margin-top: 2em; - } -} - .page-home-header { @include background(radial-gradient(color-stops(#fff, #c1f1ff), bottom)); border-bottom: 1px solid #B9DCFF; @@ -39,3 +30,27 @@ } } +// text-separator +.text-separator { + overflow: hidden; + text-align: center; + margin: 20px 0 20px 0; +} +.text-separator:before, +.text-separator:after { + background-color: #abe098; + content: ""; + display: inline-block; + height: 1px; + position: relative; + vertical-align: middle; + width: 50%; +} +.text-separator:before { + right: 0.5em; + margin-left: -50%; +} +.text-separator:after { + left: 0.5em; + margin-right: -50%; +} diff --git a/app/assets/stylesheets/theme/overrides.css.scss b/app/assets/stylesheets/theme/overrides.css.scss index 130d82c..821a55d 100644 --- a/app/assets/stylesheets/theme/overrides.css.scss +++ b/app/assets/stylesheets/theme/overrides.css.scss @@ -27,4 +27,10 @@ .active-scaffold div.popover-content { padding: 9px 14px; font-weight: normal; +} + +.btn-social:hover { + &:hover, &:focus { + color: $btn-primary-color; + } } \ No newline at end of file diff --git a/app/controllers/users/authentications_controller.rb b/app/controllers/users/authentications_controller.rb new file mode 100644 index 0000000..1ae931d --- /dev/null +++ b/app/controllers/users/authentications_controller.rb @@ -0,0 +1,6 @@ +class Users::AuthenticationsController < UsersController + active_scaffold :authentication do |conf| + conf.list.columns = [:provider] + conf.actions.exclude :create, :search, :update, :show + end +end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 0000000..802d2c8 --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -0,0 +1,78 @@ +class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController + + def google_apps + oauthorize 'google_apps' + end + + protected + + def oauthorize(provider) + @user = find_for_oauth(provider) + return unless @user + if @user.active_for_authentication? + flash[:notice] = I18n.t "devise.omniauth_callbacks.success", kind: provider.camelcase + session["devise.google_apps_data"] = env["omniauth.auth"] + @user.remember_me! if session.delete(:user_remember_me) == "1" + end + if user_signed_in? + redirect_to :back + else + sign_in_and_redirect @user, event: :authentication + end + end + + def find_for_oauth(provider) + user = if resource then resource + elsif email then find_or_create_by_email(email) + elsif uid then find_or_create_by_uid(uid) + else raise "Bad provider data: #{auth.inspect}" + end + + authentication = user.authentications.where(provider: provider).first + if authentication.nil? + authentication_attrs = authorization_attrs.merge(provider: provider) + authentication = user.authentications.build(authentication_attrs) + user.authentications << authentication + end + + return user + end + + def find_or_create_by_uid(uid) + auth = Authentication.where(uid: uid).first + return auth ? auth.user : make_user + end + + def find_or_create_by_email(email) + user = User.where(email: email).first + return user ? user : make_user + end + + def make_user + return current_user if user_signed_in? + user = User.new(user_attrs.merge(password: Devise.friendly_token[0,20])) + user.skip_confirmation! + user.save!(validate: false) + return user + end + + def auth; env["omniauth.auth"] end + def uid; @uid ||= auth['uid'] rescue nil end + def email; @email ||= auth['info']['email'] rescue nil end + + def authorization_attrs + @authorization_attrs ||= { + uid: uid, + token: auth['credentials']['token'], + secret: auth['credentials']['secret'], + name: auth['info']['name'] + } + end + + def user_attrs + @user_attrs ||= { email: email, full_name: auth['info']['name'] } + end + + def handle_unverified_request; true end + +end diff --git a/app/helpers/users/authentications_helper.rb b/app/helpers/users/authentications_helper.rb new file mode 100644 index 0000000..54788a8 --- /dev/null +++ b/app/helpers/users/authentications_helper.rb @@ -0,0 +1,15 @@ +module Users::AuthenticationsHelper + def authentication_provider_column(record, column) + case record.provider + when 'google_apps' + ret = <<-DOC + + Google + + DOC + ret.html_safe + else + record.provider.camelcase + end + end +end \ No newline at end of file diff --git a/app/models/authentication.rb b/app/models/authentication.rb new file mode 100644 index 0000000..5caf071 --- /dev/null +++ b/app/models/authentication.rb @@ -0,0 +1,3 @@ +class Authentication < ActiveRecord::Base + belongs_to :user, :inverse_of => :authentications +end diff --git a/app/models/user.rb b/app/models/user.rb index f475946..8f9868c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,7 +12,9 @@ class User < ActiveRecord::Base :trackable, :validatable, :confirmable, - :lockable + :lockable, + :omniauthable, + :omniauth_providers => [:google_apps] validates :full_name, :presence => true @@ -20,6 +22,7 @@ class User < ActiveRecord::Base # attr_accessible :email, :password, :password_confirmation, :remember_me, # :full_name + has_many :authentications, :inverse_of => :user, :dependent => :destroy has_many :domains, :inverse_of => :user, :dependent => :destroy has_many :records, :inverse_of => :user, :dependent => :destroy has_many :permissions, :inverse_of => :user, :dependent => :destroy diff --git a/app/models/user_ability.rb b/app/models/user_ability.rb index cfd5ef9..63f9919 100644 --- a/app/models/user_ability.rb +++ b/app/models/user_ability.rb @@ -32,6 +32,9 @@ class UserAbility # can manage permissions for his domains can CRUD, Permission, :domain => {:user_id => user.id} can :crud_permissions, Domain, :user_id => user.id + + # can manage his authentications + can CRUD, Authentication, :user_id => user.id end def sharing_abilities diff --git a/app/views/public/pages/home.html.erb b/app/views/public/pages/home.html.erb index b8be943..9c5db88 100644 --- a/app/views/public/pages/home.html.erb +++ b/app/views/public/pages/home.html.erb @@ -12,10 +12,7 @@
-
-
+
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), @@ -26,8 +26,12 @@ <%= f.submit "Update", class: 'btn btn-primary' %> <% end %>
-
+
+ <% if current_user.authentications.exists? %> + <%= render active_scaffold: "users/authentications" %> +

+ <% end %>

Delete account

diff --git a/app/views/users/registrations/new.html.erb b/app/views/users/registrations/new.html.erb index c5c53bf..fe96e48 100644 --- a/app/views/users/registrations/new.html.erb +++ b/app/views/users/registrations/new.html.erb @@ -1,4 +1,9 @@ -<%= render :partial => "users/shared/links" %> - -<%= render 'users/registrations/new' %> +<%= render 'users/shared/links' %> +

+
+ + <%= render 'users/registrations/new' %> + +
+
diff --git a/app/views/users/sessions/new.html.erb b/app/views/users/sessions/new.html.erb index 6f42598..5d2dbd8 100644 --- a/app/views/users/sessions/new.html.erb +++ b/app/views/users/sessions/new.html.erb @@ -1,19 +1,36 @@ -<%= render :partial => "users/shared/links" %> - -<%= simple_form_for(resource, - as: resource_name, - url: session_path(resource_name) - ) do |f| %> - - <%= f.input :email, required: true %> - <%= f.input :password, required: true %> - <% if devise_mapping.rememberable? -%> - <%= f.input :remember_me, as: :boolean %> - <% end -%> - - <%= f.submit "Sign in", class: 'btn btn-primary' %> - - or <%= link_to "Sign up", new_registration_path(resource_name) %> - / <%= link_to 'Forgot your password?', new_password_path(resource_name) %> - -<% end %> +<%= render 'users/shared/links' %> + +
+
+ +

+ <%= link_to user_omniauth_authorize_path(:google_apps), + class: 'btn btn-block btn-social btn-google-plus', + 'data-no-turbolink' => true do %> + Sign in with Google + <% end %> +

+ +
Or use the form below
+ + <%= simple_form_for(resource, + as: resource_name, + url: session_path(resource_name) + ) do |f| %> + + <%= f.input :email, required: true %> + <%= f.input :password, required: true %> + <% if devise_mapping.rememberable? -%> + <%= f.input :remember_me, as: :boolean %> + <% end -%> + + <%= f.submit "Sign in", class: 'btn btn-primary' %> + + or <%= link_to "Sign up", new_registration_path(resource_name) %> + / <%= link_to 'Forgot password', new_password_path(resource_name) %> + + + <% end %> + +
+
\ No newline at end of file diff --git a/app/views/users/shared/_links.erb b/app/views/users/shared/_links.erb index cde93bd..138c19a 100644 --- a/app/views/users/shared/_links.erb +++ b/app/views/users/shared/_links.erb @@ -1,10 +1,10 @@
-<% semantic_navigation :devise_nav, html: { class: 'nav nav-tabs' } do |n| %> +<% semantic_navigation :devise_nav, html: { class: 'nav nav-tabs nav-justified' } do |n| %> <%= n.item "Sign in", link: new_session_path(resource_name) %> <%= n.item "Sign up", link: new_registration_path(resource_name) %> - <%= n.item "Forgot your password?", link: new_password_path(resource_name), + <%= n.item "Forgot password", link: new_password_path(resource_name), highlights_on: [{ controller: 'devise/passwords' }] %> - <%= n.item "Didn't receive confirmation instructions?", link: new_confirmation_path(resource_name) %> - <%= n.item "Didn't receive unlock instructions?", link: new_unlock_path(resource_name) %> + <%= n.item "Resend confirmation instructions", link: new_confirmation_path(resource_name) %> + <%= n.item "Resend unlock instructions", link: new_unlock_path(resource_name) %> <% end %>
diff --git a/app/views/users/unlocks/new.html.erb b/app/views/users/unlocks/new.html.erb index f0741b6..3d77f9f 100644 --- a/app/views/users/unlocks/new.html.erb +++ b/app/views/users/unlocks/new.html.erb @@ -1,14 +1,20 @@ <%= render :partial => "users/shared/links" %> -<%= simple_form_for(resource, - as: resource_name, - url: unlock_path(resource_name), - method: :post - ) do |f| %> - - <%= devise_error_messages! %> - - <%= f.input :email, required: true %> - - <%= f.submit "Resend unlock instructions", class: 'btn btn-primary' %> -<% end %> +
+
+ + <%= simple_form_for(resource, + as: resource_name, + url: unlock_path(resource_name), + method: :post + ) do |f| %> + + <%= devise_error_messages! %> + + <%= f.input :email, required: true %> + + <%= f.submit "Resend unlock instructions", class: 'btn btn-primary' %> + <% end %> + +
+
\ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index f306778..aaae2c8 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,3 +1,5 @@ +require 'openid/store/filesystem' + # Use this hook to configure devise mailer, warden hooks and so forth. The first # four configuration values can also be set straight in your models. Devise.setup do |config| @@ -193,6 +195,12 @@ Devise.setup do |config| # Add a new OmniAuth provider. Check the wiki for more information on setting # up on your models and hooks. # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' + # config.omniauth :open_id, name: 'user', + # identifier: 'https://www.google.com/accounts/o8/site-xrds?hd=' + Settings.domain, + # store: OpenID::Store::Filesystem.new('/tmp') + config.omniauth :google_apps, + store: OpenID::Store::Filesystem.new('/tmp'), + domain: 'gmail.com' # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/config/routes.rb b/config/routes.rb index e820f17..1352957 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,11 +5,16 @@ Entrydns::Application.routes.draw do devise_for :admins devise_for :users, controllers: { - registrations: 'users/registrations' + registrations: 'users/registrations', + omniauth_callbacks: 'users/omniauth_callbacks' } - + scope module: 'users' do + resources :authentications do + as_routes + end + resources :domains do as_routes end diff --git a/config/settings.sample.yml b/config/settings.sample.yml index 882aa01..d37a99e 100644 --- a/config/settings.sample.yml +++ b/config/settings.sample.yml @@ -1,3 +1,5 @@ +domain: entrydns.net + # framework secret_key_base: 0ce1f02a4b3fc4d1a1c8d22973b21e8589e9314dc338294953f0b985e3f44f12c8af74f2d9ba6f7c7bdb736c4efc5ea3f8135e23b1a036d033cd23331383ac75 diff --git a/db/migrate/20140102165905_create_authentications.rb b/db/migrate/20140102165905_create_authentications.rb new file mode 100644 index 0000000..e09ad64 --- /dev/null +++ b/db/migrate/20140102165905_create_authentications.rb @@ -0,0 +1,14 @@ +class CreateAuthentications < ActiveRecord::Migration + def change + create_table :authentications do |t| + t.references :user, index: true + t.string :provider, null: false + t.string :uid, null: false + t.string :token + t.string :secret + t.string :name + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 28acc7f..65aaaf8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20130918144521) do +ActiveRecord::Schema.define(version: 20140102165905) do create_table "admins", force: true do |t| t.string "email", default: "", null: false @@ -32,6 +32,19 @@ ActiveRecord::Schema.define(version: 20130918144521) do add_index "admins", ["email"], name: "index_admins_on_email", unique: true, using: :btree add_index "admins", ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true, using: :btree + create_table "authentications", force: true do |t| + t.integer "user_id" + t.string "provider", null: false + t.string "uid", null: false + t.string "token" + t.string "secret" + t.string "name" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "authentications", ["user_id"], name: "index_authentications_on_user_id", using: :btree + create_table "blacklisted_domains", force: true do |t| t.string "name" t.datetime "created_at" diff --git a/spec/routing/users/authentications_routing_spec.rb b/spec/routing/users/authentications_routing_spec.rb new file mode 100644 index 0000000..a4a5633 --- /dev/null +++ b/spec/routing/users/authentications_routing_spec.rb @@ -0,0 +1,15 @@ +require "spec_helper" + +describe Users::AuthenticationsController do + describe "routing" do + + it "routes to #index" do + get("/authentications").should route_to("users/authentications#index") + end + + it "routes to #destroy" do + delete("/authentications/1").should route_to("users/authentications#destroy", :id => "1") + end + + end +end diff --git a/vendor/assets/stylesheets/bootstrap-social.css b/vendor/assets/stylesheets/bootstrap-social.css new file mode 100644 index 0000000..0a59a47 --- /dev/null +++ b/vendor/assets/stylesheets/bootstrap-social.css @@ -0,0 +1,57 @@ +/* + * Social Buttons for Bootstrap + * + * Copyright 2013 Panayiotis Lipiridis + * Licensed under the MIT License + * + * https://github.com/lipis/bootstrap-social + */ + +.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social :first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)} +.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg :first-child{line-height:45px;width:45px;font-size:1.8em} +.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm :first-child{line-height:28px;width:28px;font-size:1.4em} +.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs :first-child{line-height:20px;width:20px;font-size:1.2em} +.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon :first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)} +.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg :first-child{line-height:45px;width:45px;font-size:1.8em} +.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm :first-child{line-height:28px;width:28px;font-size:1.4em} +.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs :first-child{line-height:20px;width:20px;font-size:1.2em} +.btn-social-icon :first-child{border:none;text-align:center;width:100% !important} +.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0} +.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0} +.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0} +.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover,.btn-bitbucket:focus,.btn-bitbucket:active,.btn-bitbucket.active,.open .dropdown-toggle.btn-bitbucket{color:#fff;background-color:#183c60;border-color:rgba(0,0,0,0.2)} +.btn-bitbucket:active,.btn-bitbucket.active,.open .dropdown-toggle.btn-bitbucket{background-image:none} +.btn-bitbucket.disabled,.btn-bitbucket[disabled],fieldset[disabled] .btn-bitbucket,.btn-bitbucket.disabled:hover,.btn-bitbucket[disabled]:hover,fieldset[disabled] .btn-bitbucket:hover,.btn-bitbucket.disabled:focus,.btn-bitbucket[disabled]:focus,fieldset[disabled] .btn-bitbucket:focus,.btn-bitbucket.disabled:active,.btn-bitbucket[disabled]:active,fieldset[disabled] .btn-bitbucket:active,.btn-bitbucket.disabled.active,.btn-bitbucket[disabled].active,fieldset[disabled] .btn-bitbucket.active{background-color:#205081;border-color:rgba(0,0,0,0.2)} +.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover,.btn-dropbox:focus,.btn-dropbox:active,.btn-dropbox.active,.open .dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d70b7;border-color:rgba(0,0,0,0.2)} +.btn-dropbox:active,.btn-dropbox.active,.open .dropdown-toggle.btn-dropbox{background-image:none} +.btn-dropbox.disabled,.btn-dropbox[disabled],fieldset[disabled] .btn-dropbox,.btn-dropbox.disabled:hover,.btn-dropbox[disabled]:hover,fieldset[disabled] .btn-dropbox:hover,.btn-dropbox.disabled:focus,.btn-dropbox[disabled]:focus,fieldset[disabled] .btn-dropbox:focus,.btn-dropbox.disabled:active,.btn-dropbox[disabled]:active,fieldset[disabled] .btn-dropbox:active,.btn-dropbox.disabled.active,.btn-dropbox[disabled].active,fieldset[disabled] .btn-dropbox.active{background-color:#1087dd;border-color:rgba(0,0,0,0.2)} +.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover,.btn-facebook:focus,.btn-facebook:active,.btn-facebook.active,.open .dropdown-toggle.btn-facebook{color:#fff;background-color:#30487b;border-color:rgba(0,0,0,0.2)} +.btn-facebook:active,.btn-facebook.active,.open .dropdown-toggle.btn-facebook{background-image:none} +.btn-facebook.disabled,.btn-facebook[disabled],fieldset[disabled] .btn-facebook,.btn-facebook.disabled:hover,.btn-facebook[disabled]:hover,fieldset[disabled] .btn-facebook:hover,.btn-facebook.disabled:focus,.btn-facebook[disabled]:focus,fieldset[disabled] .btn-facebook:focus,.btn-facebook.disabled:active,.btn-facebook[disabled]:active,fieldset[disabled] .btn-facebook:active,.btn-facebook.disabled.active,.btn-facebook[disabled].active,fieldset[disabled] .btn-facebook.active{background-color:#3b5998;border-color:rgba(0,0,0,0.2)} +.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover,.btn-flickr:focus,.btn-flickr:active,.btn-flickr.active,.open .dropdown-toggle.btn-flickr{color:#fff;background-color:#d6006f;border-color:rgba(0,0,0,0.2)} +.btn-flickr:active,.btn-flickr.active,.open .dropdown-toggle.btn-flickr{background-image:none} +.btn-flickr.disabled,.btn-flickr[disabled],fieldset[disabled] .btn-flickr,.btn-flickr.disabled:hover,.btn-flickr[disabled]:hover,fieldset[disabled] .btn-flickr:hover,.btn-flickr.disabled:focus,.btn-flickr[disabled]:focus,fieldset[disabled] .btn-flickr:focus,.btn-flickr.disabled:active,.btn-flickr[disabled]:active,fieldset[disabled] .btn-flickr:active,.btn-flickr.disabled.active,.btn-flickr[disabled].active,fieldset[disabled] .btn-flickr.active{background-color:#ff0084;border-color:rgba(0,0,0,0.2)} +.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:hover,.btn-github:focus,.btn-github:active,.btn-github.active,.open .dropdown-toggle.btn-github{color:#fff;background-color:#303030;border-color:rgba(0,0,0,0.2)} +.btn-github:active,.btn-github.active,.open .dropdown-toggle.btn-github{background-image:none} +.btn-github.disabled,.btn-github[disabled],fieldset[disabled] .btn-github,.btn-github.disabled:hover,.btn-github[disabled]:hover,fieldset[disabled] .btn-github:hover,.btn-github.disabled:focus,.btn-github[disabled]:focus,fieldset[disabled] .btn-github:focus,.btn-github.disabled:active,.btn-github[disabled]:active,fieldset[disabled] .btn-github:active,.btn-github.disabled.active,.btn-github[disabled].active,fieldset[disabled] .btn-github.active{background-color:#444;border-color:rgba(0,0,0,0.2)} +.btn-google-plus{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google-plus:hover,.btn-google-plus:focus,.btn-google-plus:active,.btn-google-plus.active,.open .dropdown-toggle.btn-google-plus{color:#fff;background-color:#ca3523;border-color:rgba(0,0,0,0.2)} +.btn-google-plus:active,.btn-google-plus.active,.open .dropdown-toggle.btn-google-plus{background-image:none} +.btn-google-plus.disabled,.btn-google-plus[disabled],fieldset[disabled] .btn-google-plus,.btn-google-plus.disabled:hover,.btn-google-plus[disabled]:hover,fieldset[disabled] .btn-google-plus:hover,.btn-google-plus.disabled:focus,.btn-google-plus[disabled]:focus,fieldset[disabled] .btn-google-plus:focus,.btn-google-plus.disabled:active,.btn-google-plus[disabled]:active,fieldset[disabled] .btn-google-plus:active,.btn-google-plus.disabled.active,.btn-google-plus[disabled].active,fieldset[disabled] .btn-google-plus.active{background-color:#dd4b39;border-color:rgba(0,0,0,0.2)} +.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover,.btn-instagram:focus,.btn-instagram:active,.btn-instagram.active,.open .dropdown-toggle.btn-instagram{color:#fff;background-color:#335d7e;border-color:rgba(0,0,0,0.2)} +.btn-instagram:active,.btn-instagram.active,.open .dropdown-toggle.btn-instagram{background-image:none} +.btn-instagram.disabled,.btn-instagram[disabled],fieldset[disabled] .btn-instagram,.btn-instagram.disabled:hover,.btn-instagram[disabled]:hover,fieldset[disabled] .btn-instagram:hover,.btn-instagram.disabled:focus,.btn-instagram[disabled]:focus,fieldset[disabled] .btn-instagram:focus,.btn-instagram.disabled:active,.btn-instagram[disabled]:active,fieldset[disabled] .btn-instagram:active,.btn-instagram.disabled.active,.btn-instagram[disabled].active,fieldset[disabled] .btn-instagram.active{background-color:#3f729b;border-color:rgba(0,0,0,0.2)} +.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover,.btn-linkedin:focus,.btn-linkedin:active,.btn-linkedin.active,.open .dropdown-toggle.btn-linkedin{color:#fff;background-color:#005f8d;border-color:rgba(0,0,0,0.2)} +.btn-linkedin:active,.btn-linkedin.active,.open .dropdown-toggle.btn-linkedin{background-image:none} +.btn-linkedin.disabled,.btn-linkedin[disabled],fieldset[disabled] .btn-linkedin,.btn-linkedin.disabled:hover,.btn-linkedin[disabled]:hover,fieldset[disabled] .btn-linkedin:hover,.btn-linkedin.disabled:focus,.btn-linkedin[disabled]:focus,fieldset[disabled] .btn-linkedin:focus,.btn-linkedin.disabled:active,.btn-linkedin[disabled]:active,fieldset[disabled] .btn-linkedin:active,.btn-linkedin.disabled.active,.btn-linkedin[disabled].active,fieldset[disabled] .btn-linkedin.active{background-color:#007bb6;border-color:rgba(0,0,0,0.2)} +.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover,.btn-pinterest:focus,.btn-pinterest:active,.btn-pinterest.active,.open .dropdown-toggle.btn-pinterest{color:#fff;background-color:#a81a20;border-color:rgba(0,0,0,0.2)} +.btn-pinterest:active,.btn-pinterest.active,.open .dropdown-toggle.btn-pinterest{background-image:none} +.btn-pinterest.disabled,.btn-pinterest[disabled],fieldset[disabled] .btn-pinterest,.btn-pinterest.disabled:hover,.btn-pinterest[disabled]:hover,fieldset[disabled] .btn-pinterest:hover,.btn-pinterest.disabled:focus,.btn-pinterest[disabled]:focus,fieldset[disabled] .btn-pinterest:focus,.btn-pinterest.disabled:active,.btn-pinterest[disabled]:active,fieldset[disabled] .btn-pinterest:active,.btn-pinterest.disabled.active,.btn-pinterest[disabled].active,fieldset[disabled] .btn-pinterest.active{background-color:#cb2027;border-color:rgba(0,0,0,0.2)} +.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover,.btn-tumblr:focus,.btn-tumblr:active,.btn-tumblr.active,.open .dropdown-toggle.btn-tumblr{color:#fff;background-color:#1f3346;border-color:rgba(0,0,0,0.2)} +.btn-tumblr:active,.btn-tumblr.active,.open .dropdown-toggle.btn-tumblr{background-image:none} +.btn-tumblr.disabled,.btn-tumblr[disabled],fieldset[disabled] .btn-tumblr,.btn-tumblr.disabled:hover,.btn-tumblr[disabled]:hover,fieldset[disabled] .btn-tumblr:hover,.btn-tumblr.disabled:focus,.btn-tumblr[disabled]:focus,fieldset[disabled] .btn-tumblr:focus,.btn-tumblr.disabled:active,.btn-tumblr[disabled]:active,fieldset[disabled] .btn-tumblr:active,.btn-tumblr.disabled.active,.btn-tumblr[disabled].active,fieldset[disabled] .btn-tumblr.active{background-color:#2c4762;border-color:rgba(0,0,0,0.2)} +.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover,.btn-twitter:focus,.btn-twitter:active,.btn-twitter.active,.open .dropdown-toggle.btn-twitter{color:#fff;background-color:#309aea;border-color:rgba(0,0,0,0.2)} +.btn-twitter:active,.btn-twitter.active,.open .dropdown-toggle.btn-twitter{background-image:none} +.btn-twitter.disabled,.btn-twitter[disabled],fieldset[disabled] .btn-twitter,.btn-twitter.disabled:hover,.btn-twitter[disabled]:hover,fieldset[disabled] .btn-twitter:hover,.btn-twitter.disabled:focus,.btn-twitter[disabled]:focus,fieldset[disabled] .btn-twitter:focus,.btn-twitter.disabled:active,.btn-twitter[disabled]:active,fieldset[disabled] .btn-twitter:active,.btn-twitter.disabled.active,.btn-twitter[disabled].active,fieldset[disabled] .btn-twitter.active{background-color:#55acee;border-color:rgba(0,0,0,0.2)} +.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:hover,.btn-vk:focus,.btn-vk:active,.btn-vk.active,.open .dropdown-toggle.btn-vk{color:#fff;background-color:#4a6a89;border-color:rgba(0,0,0,0.2)} +.btn-vk:active,.btn-vk.active,.open .dropdown-toggle.btn-vk{background-image:none} +.btn-vk.disabled,.btn-vk[disabled],fieldset[disabled] .btn-vk,.btn-vk.disabled:hover,.btn-vk[disabled]:hover,fieldset[disabled] .btn-vk:hover,.btn-vk.disabled:focus,.btn-vk[disabled]:focus,fieldset[disabled] .btn-vk:focus,.btn-vk.disabled:active,.btn-vk[disabled]:active,fieldset[disabled] .btn-vk:active,.btn-vk.disabled.active,.btn-vk[disabled].active,fieldset[disabled] .btn-vk.active{background-color:#587ea3;border-color:rgba(0,0,0,0.2)}