merges master and fix conflicts

This commit is contained in:
kikito
2015-08-25 19:31:25 +02:00
41 changed files with 572 additions and 96 deletions

View File

@@ -1,7 +1,7 @@
source 'https://rubygems.org' source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.3' gem 'rails', '4.2.4'
# Use PostgreSQL # Use PostgreSQL
gem 'pg' gem 'pg'
# Use SCSS for stylesheets # Use SCSS for stylesheets
@@ -21,6 +21,11 @@ gem 'turbolinks'
gem 'devise' gem 'devise'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7' # gem 'bcrypt', '~> 3.1.7'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
gem 'kaminari' gem 'kaminari'
gem 'acts_as_commentable_with_threading' gem 'acts_as_commentable_with_threading'
gem 'acts-as-taggable-on' gem 'acts-as-taggable-on'
@@ -32,7 +37,7 @@ gem 'simple_captcha2', require: 'simple_captcha'
gem 'ckeditor' gem 'ckeditor'
gem 'cancancan' gem 'cancancan'
gem 'social-share-button' gem 'social-share-button'
gem 'initialjs-rails' gem 'initialjs-rails', '0.2.0'
gem 'unicorn' gem 'unicorn'
gem 'paranoia' gem 'paranoia'

View File

@@ -1,36 +1,36 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionmailer (4.2.3) actionmailer (4.2.4)
actionpack (= 4.2.3) actionpack (= 4.2.4)
actionview (= 4.2.3) actionview (= 4.2.4)
activejob (= 4.2.3) activejob (= 4.2.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.3) actionpack (4.2.4)
actionview (= 4.2.3) actionview (= 4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.3) actionview (4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.3) activejob (4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.3) activemodel (4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.3) activerecord (4.2.4)
activemodel (= 4.2.3) activemodel (= 4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
arel (~> 6.0) arel (~> 6.0)
activesupport (4.2.3) activesupport (4.2.4)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
minitest (~> 5.1) minitest (~> 5.1)
@@ -133,6 +133,8 @@ GEM
factory_girl_rails (4.5.0) factory_girl_rails (4.5.0)
factory_girl (~> 4.5.0) factory_girl (~> 4.5.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
foundation-rails (5.5.2.1) foundation-rails (5.5.2.1)
railties (>= 3.1.0) railties (>= 3.1.0)
sass (>= 3.3.0, < 3.5) sass (>= 3.3.0, < 3.5)
@@ -150,6 +152,7 @@ GEM
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
groupdate (2.4.0) groupdate (2.4.0)
activesupport (>= 3) activesupport (>= 3)
hashie (3.4.2)
highline (1.7.3) highline (1.7.3)
http-cookie (1.0.2) http-cookie (1.0.2)
domain_name (~> 0.5) domain_name (~> 0.5)
@@ -162,13 +165,14 @@ GEM
i18n i18n
term-ansicolor (>= 1.3.2) term-ansicolor (>= 1.3.2)
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
initialjs-rails (0.1.0) initialjs-rails (0.2.0)
railties (>= 3.1, < 5.0) railties (>= 3.1, < 5.0)
jquery-rails (4.0.4) jquery-rails (4.0.4)
rails-dom-testing (~> 1.0) rails-dom-testing (~> 1.0)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (1.8.3) json (1.8.3)
jwt (1.5.1)
kaminari (0.16.3) kaminari (0.16.3)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
@@ -181,7 +185,7 @@ GEM
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
loofah (2.0.2) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
@@ -189,12 +193,38 @@ GEM
mini_portile (0.6.2) mini_portile (0.6.2)
minitest (5.8.0) minitest (5.8.0)
multi_json (1.11.2) multi_json (1.11.2)
multi_xml (0.5.5)
multipart-post (2.0.0)
net-scp (1.2.1) net-scp (1.2.1)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (2.9.2) net-ssh (2.9.2)
netrc (0.10.3) netrc (0.10.3)
nokogiri (1.6.6.2) nokogiri (1.6.6.2)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
oauth (0.4.7)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
omniauth-facebook (2.0.1)
omniauth-oauth2 (~> 1.2)
omniauth-google-oauth2 (0.2.6)
omniauth (> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-oauth (1.1.0)
oauth
omniauth (~> 1.0)
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-twitter (1.2.1)
json (~> 1.3)
omniauth-oauth (~> 1.1)
orm_adapter (0.5.0) orm_adapter (0.5.0)
paranoia (2.1.3) paranoia (2.1.3)
activerecord (~> 4.0) activerecord (~> 4.0)
@@ -209,28 +239,28 @@ GEM
rack (1.6.4) rack (1.6.4)
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.3) rails (4.2.4)
actionmailer (= 4.2.3) actionmailer (= 4.2.4)
actionpack (= 4.2.3) actionpack (= 4.2.4)
actionview (= 4.2.3) actionview (= 4.2.4)
activejob (= 4.2.3) activejob (= 4.2.4)
activemodel (= 4.2.3) activemodel (= 4.2.4)
activerecord (= 4.2.3) activerecord (= 4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.3) railties (= 4.2.4)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.6) rails-dom-testing (1.0.7)
activesupport (>= 4.2.0.beta, < 5.0) activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0) nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.2) rails-html-sanitizer (1.0.2)
loofah (~> 2.0) loofah (~> 2.0)
railties (4.2.3) railties (4.2.4)
actionpack (= 4.2.3) actionpack (= 4.2.4)
activesupport (= 4.2.3) activesupport (= 4.2.4)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
raindrops (0.15.0) raindrops (0.15.0)
@@ -283,7 +313,7 @@ GEM
coffee-rails coffee-rails
sass-rails sass-rails
spring (1.3.6) spring (1.3.6)
sprockets (3.2.0) sprockets (3.3.3)
rack (~> 1.0) rack (~> 1.0)
sprockets-rails (2.3.2) sprockets-rails (2.3.2)
actionpack (>= 3.0) actionpack (>= 3.0)
@@ -357,16 +387,20 @@ DEPENDENCIES
fuubar fuubar
groupdate groupdate
i18n-tasks i18n-tasks
initialjs-rails initialjs-rails (= 0.2.0)
jquery-rails jquery-rails
kaminari kaminari
launchy launchy
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
omniauth
omniauth-facebook
omniauth-google-oauth2
omniauth-twitter
paranoia paranoia
pg pg
poltergeist poltergeist
quiet_assets quiet_assets
rails (= 4.2.3) rails (= 4.2.4)
responders responders
rspec-rails (~> 3.0) rspec-rails (~> 3.0)
sass-rails (~> 5.0) sass-rails (~> 5.0)

View File

@@ -44,6 +44,12 @@ Para ejecutar los tests:
bundle exec bin/rspec bundle exec bin/rspec
``` ```
### OAuth
Para probar los servicios de autenticación mediante proveedores externos OAuth — en este momento Twitter, Facebook y Google —, necesitas crear una "aplicación" en cada una de las plataformas soportadas y configurar la *key* y el *secret* proporcionados en tu *secrets.yml*
En el caso de Google, comprueba que las APIs *Contacts API* y *Google+ API* están habilitadas para la aplicación.
## Licencia ## Licencia
El código de este proyecto está publicado bajo la licencia AFFERO GPL v3 (ver [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt)) El código de este proyecto está publicado bajo la licencia AFFERO GPL v3 (ver [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt))

View File

@@ -152,7 +152,6 @@ h1, h2, h3, h4, h5, h6 {
} }
.f-dropdown { .f-dropdown {
li a { li a {
font-size: rem-calc(12); font-size: rem-calc(12);
@@ -170,6 +169,19 @@ h1, h2, h3, h4, h5, h6 {
} }
} }
.margin {
margin-top: $line-height;
margin-bottom: $line-height;
}
.margin-top {
margin-top: $line-height;
}
.margin-bottom {
margin-bottom: $line-height;
}
// 04. Header // 04. Header
// - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -514,10 +526,15 @@ footer {
h2 { h2 {
clear: both; clear: both;
font-size: rem-calc(30); font-size: rem-calc(18);
font-weight: bold; font-weight: bold;
line-height: $line-height;
@media (min-width: $small-breakpoint) {
font-size: rem-calc(30);
line-height: $line-height*2; line-height: $line-height*2;
} }
}
.back, .icon-angle-left { .back, .icon-angle-left {
@include back; @include back;

View File

@@ -85,6 +85,7 @@ $comment-official: rgba(70,219,145,.3);
// 06. Responsive // 06. Responsive
// - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - -
$small: em-calc(480);
$small-breakpoint: em-calc(640); $small-breakpoint: em-calc(640);
$medium-breakpoint: em-calc(1024); $medium-breakpoint: em-calc(1024);
$large-breakpoint: em-calc(1440); $large-breakpoint: em-calc(1440);

View File

@@ -13,6 +13,8 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead. # For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception protect_from_forgery with: :exception
before_action :ensure_signup_complete
rescue_from CanCan::AccessDenied do |exception| rescue_from CanCan::AccessDenied do |exception|
redirect_to main_app.root_url, alert: exception.message redirect_to main_app.root_url, alert: exception.message
end end
@@ -40,4 +42,13 @@ class ApplicationController < ActionController::Base
def set_debate_votes(debates) def set_debate_votes(debates)
@voted_values = current_user ? current_user.debate_votes(debates) : {} @voted_values = current_user ? current_user.debate_votes(debates) : {}
end end
def ensure_signup_complete
# Ensure we don't go into an infinite loop
return if action_name.in? %w(finish_signup do_finish_signup)
if user_signed_in? && !current_user.email_provided?
redirect_to finish_signup_path
end
end
end end

View File

@@ -5,6 +5,9 @@ class Organizations::RegistrationsController < Devise::RegistrationsController
end end
end end
def success
end
def create def create
build_resource(sign_up_params) build_resource(sign_up_params)
if resource.valid_with_captcha? if resource.valid_with_captcha?
@@ -17,6 +20,11 @@ class Organizations::RegistrationsController < Devise::RegistrationsController
end end
end end
protected
def after_inactive_sign_up_path_for(resource)
organizations_sign_up_success_path
end
private private
def sign_up_params def sign_up_params

View File

@@ -0,0 +1,29 @@
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
@user = User.find_for_oauth(env["omniauth.auth"], current_user)
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook, :google_oauth2].each do |provider|
provides_callback_for provider
end
def after_sign_in_path_for(resource)
if resource.email_provided?
super(resource)
else
finish_signup_path
end
end
end

View File

@@ -1,4 +1,5 @@
class Users::RegistrationsController < Devise::RegistrationsController class Users::RegistrationsController < Devise::RegistrationsController
prepend_before_filter :authenticate_scope!, only: [:edit, :update, :destroy, :finish_signup, :do_finish_signup]
def create def create
build_resource(sign_up_params) build_resource(sign_up_params)
@@ -9,6 +10,19 @@ class Users::RegistrationsController < Devise::RegistrationsController
end end
end end
def finish_signup
end
def do_finish_signup
if current_user.update(sign_up_params)
current_user.skip_reconfirmation!
sign_in(current_user, bypass: true)
redirect_to root_url
else
render :finish_signup
end
end
private private
def sign_up_params def sign_up_params

17
app/models/identity.rb Normal file
View File

@@ -0,0 +1,17 @@
class Identity < ActiveRecord::Base
belongs_to :user
validates :provider, presence: true
validates :uid, presence: true, uniqueness: { scope: :provider }
def self.find_for_oauth(auth)
where(uid: auth.uid, provider: auth.provider).first_or_create
end
def update_user(new_user)
return unless user != new_user
self.user = new_user
save!
end
end

View File

@@ -1,8 +1,12 @@
class User < ActiveRecord::Base class User < ActiveRecord::Base
include ActsAsParanoidAliases include ActsAsParanoidAliases
OMNIAUTH_EMAIL_PREFIX = 'omniauth@participacion'
OMNIAUTH_EMAIL_REGEX = /\A#{OMNIAUTH_EMAIL_PREFIX}/
apply_simple_captcha apply_simple_captcha
devise :database_authenticatable, :registerable, :confirmable, devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable :recoverable, :rememberable, :trackable, :validatable, :omniauthable
acts_as_voter acts_as_voter
acts_as_paranoid column: :hidden_at acts_as_paranoid column: :hidden_at
@@ -11,9 +15,11 @@ class User < ActiveRecord::Base
has_one :moderator has_one :moderator
has_one :organization has_one :organization
has_many :inappropiate_flags has_many :inappropiate_flags
has_many :identities, dependent: :destroy
validates :username, presence: true, unless: :organization? validates :username, presence: true, unless: :organization?
validates :official_level, inclusion: {in: 0..5} validates :official_level, inclusion: {in: 0..5}
validates_format_of :email, without: OMNIAUTH_EMAIL_REGEX, on: :update
validates_associated :organization, message: false validates_associated :organization, message: false
@@ -25,6 +31,43 @@ class User < ActiveRecord::Base
scope :organizations, -> { joins(:organization) } scope :organizations, -> { joins(:organization) }
scope :officials, -> { where("official_level > 0") } scope :officials, -> { where("official_level > 0") }
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
user ||= first_or_create_for_oauth(auth)
# Associate the identity with the user if needed
identity.update_user(user)
user
end
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via RegistrationsController.finish_signup
def self.first_or_create_for_oauth(auth)
email = auth.info.email if auth.info.verified || auth.info.verified_email
user = User.where(email: email).first if email
# Create the user if it's a new registration
if user.nil?
user = User.new(
username: auth.info.nickname || auth.extra.raw_info.name.parameterize('-') || auth.uid,
email: email ? email : "#{OMNIAUTH_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
end
user
end
def name def name
organization? ? organization.name : username organization? ? organization.name : username
end end
@@ -67,4 +110,8 @@ class User < ActiveRecord::Base
e.present? ? where(email: e) : none e.present? ? where(email: e) : none
end end
def email_provided?
!!(email && email !~ OMNIAUTH_EMAIL_REGEX) ||
!!(unconfirmed_email && unconfirmed_email !~ OMNIAUTH_EMAIL_REGEX)
end
end end

View File

@@ -26,7 +26,7 @@
<div class="small-12 medium-6 column"> <div class="small-12 medium-6 column">
<h2><%= t("account.show.avatar")%></h2> <h2><%= t("account.show.avatar")%></h2>
<%= avatar_image(@account, size: 100) %> <%= avatar_image(@account, seed: @account.id, size: 100) %>
<h2><%= t("account.show.notifications")%></h2> <h2><%= t("account.show.notifications")%></h2>

View File

@@ -14,7 +14,7 @@
<% if comment.user.organization? %> <% if comment.user.organization? %>
<%= image_tag("collective_avatar.png", size: 32, class: "avatar left") %> <%= image_tag("collective_avatar.png", size: 32, class: "avatar left") %>
<% else %> <% else %>
<%= avatar_image(comment.user, size: 32, class: "left") %> <%= avatar_image(comment.user, seed: comment.user_id, size: 32, class: "left") %>
<% end %> <% end %>
<% if comment.user.hidden? %> <% if comment.user.hidden? %>
<i class="icon-deleted user-deleted"></i> <i class="icon-deleted user-deleted"></i>

View File

@@ -13,7 +13,7 @@
<h1><%= @debate.title %></h1> <h1><%= @debate.title %></h1>
<div class="debate-info"> <div class="debate-info">
<%= avatar_image(@debate.author, size: 32, class: 'author-photo') %> <%= avatar_image(@debate.author, seed: @debate.author_id, size: 32, class: 'author-photo') %>
<% if @debate.author.hidden? %> <% if @debate.author.hidden? %>
<i class="icon-deleted author-deleted"></i> <i class="icon-deleted author-deleted"></i>
<span class="author"> <span class="author">

View File

@@ -0,0 +1,7 @@
<br/>
<%= link_to t("omniauth.twitter.sign_in"), user_omniauth_authorize_path(:twitter), class: 'button radius expand' %>
<%= link_to t("omniauth.facebook.sign_in"), user_omniauth_authorize_path(:facebook), class: 'button radius expand' %>
<%= link_to t("omniauth.google_oauth2.sign_in"), user_omniauth_authorize_path(:google_oauth2), class: 'button radius expand' %>
<hr/>

View File

@@ -3,6 +3,8 @@
<div class="panel"> <div class="panel">
<h2><%= t("devise_views.sessions.new.title") %></h2> <h2><%= t("devise_views.sessions.new.title") %></h2>
<%= render 'devise/omniauth_form' %>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="row"> <div class="row">
<div class="small-12 columns"> <div class="small-12 columns">

View File

@@ -25,7 +25,7 @@
<%= link_to t("devise_views.shared.links.new_unlock"), new_unlock_path(resource_name) %><br> <%= link_to t("devise_views.shared.links.new_unlock"), new_unlock_path(resource_name) %><br>
<% end -%> <% end -%>
<%- if devise_mapping.omniauthable? %> <%- if devise_mapping.omniauthable? && devise_mapping.name == 'user' %>
<%- resource_class.omniauth_providers.each do |provider| %> <%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to t("devise_views.shared.links.signin_with_provider", provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider) %><br> <%= link_to t("devise_views.shared.links.signin_with_provider", provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider) %><br>
<% end -%> <% end -%>

View File

@@ -0,0 +1,15 @@
<div class="auth row">
<div class="small-12 medium-8 column small-centered">
<div class="panel">
<h2><%= t("devise_views.organizations.registrations.success.title") %></h2>
<p><%= t("devise_views.organizations.registrations.success.thank_you") %></p>
<p><%= t("devise_views.organizations.registrations.success.instructions_1_html") %></p>
<p><%= t("devise_views.organizations.registrations.success.instructions_2_html") %></p>
<p><%= t("devise_views.organizations.registrations.success.instructions_3_html") %></p>
<p>
<%= link_to t("devise_views.organizations.registrations.success.back_to_index"),
root_path, class: "button radius small margin-top" %>
</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,13 @@
<div class="auth row">
<div class="small-12 medium-8 large-5 column small-centered">
<div class="panel">
<h1><%= t('omniauth.finish_signup.title') %></h1>
<%= form_for current_user, as: :user, url: do_finish_signup_path, html: { role: 'form'} do |f| %>
<%= render 'shared/errors', resource: current_user %>
<%= f.email_field :email, placeholder: t("devise_views.users.registrations.new.email_label"), value: nil %>
<%= f.submit t("devise_views.users.registrations.new.submit"), class: 'button radius' %>
<% end %>
</div>
</div>
</div>

View File

@@ -2,6 +2,9 @@
<div class="small-12 medium-8 large-5 column small-centered"> <div class="small-12 medium-8 large-5 column small-centered">
<div class="panel"> <div class="panel">
<h2><%= t("devise_views.users.registrations.new.title") %></h2> <h2><%= t("devise_views.users.registrations.new.title") %></h2>
<%= render 'devise/omniauth_form' %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render 'shared/errors', resource: resource %> <%= render 'shared/errors', resource: resource %>

View File

@@ -2,4 +2,10 @@
recaptcha_public_key: <%= ENV["MADRID_RECAPTCHA_PUBLIC_KEY"] %> recaptcha_public_key: <%= ENV["MADRID_RECAPTCHA_PUBLIC_KEY"] %>
recaptcha_private_key: <%= ENV["MADRID_RECAPTCHA_PRIVATE_KEY"] %> recaptcha_private_key: <%= ENV["MADRID_RECAPTCHA_PRIVATE_KEY"] %>
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
twitter_key: <%= ENV["TWITTER_KEY"] %>
twitter_secret: <%= ENV["TWITTER_SECRET"] %>
facebook_key: <%= ENV["FACEBOOK_KEY"] %>
facebook_secret: <%= ENV["FACEBOOK_SECRET"] %>
google_oauth2_key: <%= ENV["GOOGLE_KEY"] %>
google_oauth2_secret: <%= ENV["GOOGLE_SECRET"] %>
server_name: <%= fetch(:server_name) %> server_name: <%= fetch(:server_name) %>

View File

@@ -42,7 +42,7 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information # Use the lowest log level to ensure availability of diagnostic information
# when problems arise. # when problems arise.

View File

@@ -42,7 +42,7 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information # Use the lowest log level to ensure availability of diagnostic information
# when problems arise. # when problems arise.

View File

@@ -239,6 +239,9 @@ Devise.setup do |config|
# Add a new OmniAuth provider. Check the wiki for more information on setting # Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks. # up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
config.omniauth :twitter, Rails.application.secrets.twitter_key, Rails.application.secrets.twitter_secret
config.omniauth :facebook, Rails.application.secrets.facebook_key, Rails.application.secrets.facebook_secret
config.omniauth :google_oauth2, Rails.application.secrets.google_oauth2_key, Rails.application.secrets.google_oauth2_secret
# ==> Warden configuration # ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or # If you want to use other strategies, that are not supported by Devise, or

View File

@@ -66,6 +66,13 @@ en:
phone_number_label: "Phone number" phone_number_label: "Phone number"
password_confirmation_label: "Confirm password" password_confirmation_label: "Confirm password"
submit: "Sign up" submit: "Sign up"
success:
title: "Registration of organization / collective"
thank_you: "Thank you for registering your organization or collective in the website. Now is <b>pending verification</b>."
instructions_1_html: "We will <b>contact you soon</b> in order to verify that you represent your collective."
instructions_2_html: "Meanwhile, <b>review your email</b>. We have sent you a <b>confirmation link to activate your account</b>."
instructions_3_html: "When you confirm your account will then be able to participate as a non-verified organization."
back_to_index: "Ok, go back to index"
sessions: sessions:
new: new:
title: "Log in" title: "Log in"

View File

@@ -66,6 +66,13 @@ es:
phone_number_label: "Teléfono" phone_number_label: "Teléfono"
password_confirmation_label: "Confirmar contraseña" password_confirmation_label: "Confirmar contraseña"
submit: "Registrarse" submit: "Registrarse"
success:
title: "Registro de organización / colectivo"
thank_you: "Gracias por registrar tu colectivo en la web. Ahora está <b>pendiente de verificación</b>."
instructions_1_html: "En breve <b>nos pondremos en contacto contigo</b> para verificar que realmente representas a este colectivo."
instructions_2_html: "Mientras <b>revisa tu correo electrónico</b>, te hemos enviado un <b>enlace para confirmar tu cuenta</b>."
instructions_3_html: "Una vez confirmado, podrás empezar a participar como colectivo no verificado."
back_to_index: "Entendido, volver a la página principal"
sessions: sessions:
new: new:
title: "Entrar" title: "Entrar"

View File

@@ -148,3 +148,12 @@ en:
all: "You are not authorized to %{action} %{subject}." all: "You are not authorized to %{action} %{subject}."
welcome: welcome:
last_debates: Last debates last_debates: Last debates
omniauth:
finish_signup:
title: Add Email
twitter:
sign_in: Sign in with Twitter
facebook:
sign_in: Sign in with Facebook
google_oauth2:
sign_in: Sign in with Google

View File

@@ -148,3 +148,12 @@ es:
all: "No tienes permiso para realizar la acción '%{action}' sobre %{subject}." all: "No tienes permiso para realizar la acción '%{action}' sobre %{subject}."
welcome: welcome:
last_debates: Últimos debates last_debates: Últimos debates
omniauth:
finish_signup:
title: Añade tu email
twitter:
sign_in: Entra con Twitter
facebook:
sign_in: Entra con Facebook
google_oauth2:
sign_in: Entra con Google

View File

@@ -1,10 +1,23 @@
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :users, controllers: { registrations: 'users/registrations' } devise_for :users, controllers: {
registrations: 'users/registrations',
omniauth_callbacks: 'users/omniauth_callbacks'
}
devise_for :organizations, class_name: 'User', devise_for :organizations, class_name: 'User',
controllers: { controllers: {
registrations: 'organizations/registrations', registrations: 'organizations/registrations',
sessions: 'devise/sessions' sessions: 'devise/sessions',
} },
skip: [:omniauth_callbacks]
devise_scope :organization do
get "organizations/sign_up/success", to: "organizations/registrations#success"
end
devise_scope :user do
get :finish_signup, to: 'users/registrations#finish_signup'
patch :do_finish_signup, to: 'users/registrations#do_finish_signup'
end
# The priority is based upon order of creation: first created -> highest priority. # The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes". # See how all your routes lay out with "rake routes".

View File

@@ -14,12 +14,30 @@ default: &default
development: development:
secret_key_base: 56792feef405a59b18ea7db57b4777e855103882b926413d4afdfb8c0ea8aa86ea6649da4e729c5f5ae324c0ab9338f789174cf48c544173bc18fdc3b14262e4 secret_key_base: 56792feef405a59b18ea7db57b4777e855103882b926413d4afdfb8c0ea8aa86ea6649da4e729c5f5ae324c0ab9338f789174cf48c544173bc18fdc3b14262e4
twitter_key: AAAA
twitter_secret: BBBB
facebook_key: AAAA
facebook_secret: BBBB
google_oauth2_key: AAAA
google_oauth2_secret: BBBB
<<: *default <<: *default
test: test:
secret_key_base: 4d5adf961ddd27aef19622d6c0b3234d555f9ee003f022b1f829c92bbe33aaee907be7feb67bd54c14a1a32512fa968565ad405971fbc41bd0797af73c26a796 secret_key_base: 4d5adf961ddd27aef19622d6c0b3234d555f9ee003f022b1f829c92bbe33aaee907be7feb67bd54c14a1a32512fa968565ad405971fbc41bd0797af73c26a796
twitter_key: AAAA
twitter_secret: BBBB
facebook_key: AAAA
facebook_secret: BBBB
google_oauth2_key: AAAA
google_oauth2_secret: BBBB
<<: *default <<: *default
production: production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
twitter_key: <%= ENV["TWITTER_KEY"] %>
twitter_secret: <%= ENV["TWITTER_SECRET"] %>
facebook_key: <%= ENV["FACEBOOK_KEY"] %>
facebook_secret: <%= ENV["FACEBOOK_SECRET"] %>
google_oauth2_key: <%= ENV["GOOGLE_KEY"] %>
google_oauth2_secret: <%= ENV["GOOGLE_SECRET"] %>
<<: *default <<: *default

View File

@@ -0,0 +1,11 @@
class CreateIdentities < ActiveRecord::Migration
def change
create_table :identities do |t|
t.references :user, index: true, foreign_key: true
t.string :provider
t.string :uid
t.timestamps null: false
end
end
end

View File

@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150824113326) do ActiveRecord::Schema.define(version: 20150824144524) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -66,8 +66,8 @@ ActiveRecord::Schema.define(version: 20150824113326) do
t.integer "author_id" t.integer "author_id"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.datetime "hidden_at"
t.string "visit_id" t.string "visit_id"
t.datetime "hidden_at"
t.datetime "flagged_as_inappropiate_at" t.datetime "flagged_as_inappropiate_at"
t.integer "inappropiate_flags_count", default: 0 t.integer "inappropiate_flags_count", default: 0
t.datetime "reviewed_at" t.datetime "reviewed_at"
@@ -75,6 +75,16 @@ ActiveRecord::Schema.define(version: 20150824113326) do
add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree
create_table "identities", force: :cascade do |t|
t.integer "user_id"
t.string "provider"
t.string "uid"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
create_table "inappropiate_flags", force: :cascade do |t| create_table "inappropiate_flags", force: :cascade do |t|
t.integer "user_id" t.integer "user_id"
t.string "flaggable_type" t.string "flaggable_type"
@@ -156,10 +166,10 @@ ActiveRecord::Schema.define(version: 20150824113326) do
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.boolean "email_on_debate_comment", default: false t.boolean "email_on_debate_comment", default: false
t.boolean "email_on_comment_reply", default: false t.boolean "email_on_comment_reply", default: false
t.string "phone_number", limit: 30
t.string "official_position" t.string "official_position"
t.integer "official_level", default: 0 t.integer "official_level", default: 0
t.datetime "hidden_at" t.datetime "hidden_at"
t.string "phone_number", limit: 30
t.string "username" t.string "username"
end end
@@ -214,6 +224,7 @@ ActiveRecord::Schema.define(version: 20150824113326) do
add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree
add_foreign_key "administrators", "users" add_foreign_key "administrators", "users"
add_foreign_key "identities", "users"
add_foreign_key "inappropiate_flags", "users" add_foreign_key "inappropiate_flags", "users"
add_foreign_key "moderators", "users" add_foreign_key "moderators", "users"
add_foreign_key "organizations", "users" add_foreign_key "organizations", "users"

View File

@@ -1,5 +1,4 @@
FactoryGirl.define do FactoryGirl.define do
factory :user do factory :user do
username 'Manuela' username 'Manuela'
sequence(:email) { |n| "manuela#{n}@madrid.es" } sequence(:email) { |n| "manuela#{n}@madrid.es" }
@@ -7,6 +6,12 @@ FactoryGirl.define do
confirmed_at { Time.now } confirmed_at { Time.now }
end end
factory :identity do
user nil
provider "Twitter"
uid "MyString"
end
factory :debate do factory :debate do
sequence(:title) { |n| "Debate #{n} title" } sequence(:title) { |n| "Debate #{n} title" }
description 'Debate description' description 'Debate description'

View File

@@ -2,6 +2,7 @@ require 'rails_helper'
feature 'Users' do feature 'Users' do
context 'Regular authentication' do
scenario 'Sign up' do scenario 'Sign up' do
visit '/' visit '/'
click_link 'Sign up' click_link 'Sign up'
@@ -41,6 +42,120 @@ feature 'Users' do
expect(page).to have_content 'Signed in successfully.' expect(page).to have_content 'Signed in successfully.'
end end
end
context 'OAuth authentication' do
context 'Twitter' do
background do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
scenario 'Sign up, when email was provided by OAuth provider' do
omniauth_twitter_hash = { 'provider' => 'twitter',
'uid' => '12345',
'info' => {
'name' => 'manuela',
'email' => 'manuelacarmena@example.com',
'nickname' => 'ManuelaRocks',
'verified' => '1'
},
'extra' => { 'raw_info' =>
{ 'location' => 'Madrid',
'name' => 'Manuela de las Carmenas'
}
}
}
OmniAuth.config.add_mock(:twitter, omniauth_twitter_hash)
visit '/'
click_link 'Sign up'
expect do
expect do
expect do
click_link 'Sign in with Twitter'
end.not_to change { ActionMailer::Base.deliveries.size }
end.to change { Identity.count }.by(1)
end.to change { User.count }.by(1)
expect(current_path).to eq(root_path)
expect_to_be_signed_in
user = User.last
expect(user.username).to eq('ManuelaRocks')
expect(user.email).to eq('manuelacarmena@example.com')
expect(user.confirmed?).to eq(true)
end
scenario 'Sign up, when neither email nor nickname were provided by OAuth provider' do
omniauth_twitter_hash = { 'provider' => 'twitter',
'uid' => '12345',
'info' => {
'name' => 'manuela'
},
'extra' => { 'raw_info' =>
{ 'location' => 'Madrid',
'name' => 'Manuela de las Carmenas'
}
}
}
OmniAuth.config.add_mock(:twitter, omniauth_twitter_hash)
visit '/'
click_link 'Sign up'
expect do
expect do
expect do
click_link 'Sign in with Twitter'
end.not_to change { ActionMailer::Base.deliveries.size }
end.to change { Identity.count }.by(1)
end.to change { User.count }.by(1)
expect(current_path).to eq(finish_signup_path)
user = User.last
expect(user.username).to eq('manuela-de-las-carmenas')
expect(user.email).to eq("omniauth@participacion-12345-twitter.com")
fill_in 'user_email', with: 'manueladelascarmenas@example.com'
click_button 'Sign up'
sent_token = /.*confirmation_token=(.*)".*/.match(ActionMailer::Base.deliveries.last.body.to_s)[1]
visit user_confirmation_path(confirmation_token: sent_token)
expect(page).to have_content "Your email address has been successfully confirmed"
expect(user.reload.email).to eq('manueladelascarmenas@example.com')
end
scenario 'Sign in, user was already signed up with OAuth' do
user = create(:user, email: 'manuela@madrid.es', password: 'judgementday')
identity = create(:identity, uid: '12345', provider: 'twitter', user: user)
omniauth_twitter_hash = { 'provider' => 'twitter',
'uid' => '12345',
'info' => {
'name' => 'manuela'
}
}
OmniAuth.config.add_mock(:twitter, omniauth_twitter_hash)
visit '/'
click_link 'Log in'
expect do
expect do
click_link 'Sign in with Twitter'
end.not_to change { Identity.count }
end.not_to change { User.count }
expect_to_be_signed_in
end
end
end
scenario 'Sign out' do scenario 'Sign out' do
user = create(:user) user = create(:user)
@@ -73,5 +188,4 @@ feature 'Users' do
expect(page).to have_content "Your password has been changed successfully. You are now signed in." expect(page).to have_content "Your password has been changed successfully. You are now signed in."
end end
end end

View File

@@ -0,0 +1,9 @@
require 'rails_helper'
RSpec.describe Identity, type: :model do
let(:identity) { build(:identity) }
it "should be valid" do
expect(identity).to be_valid
end
end

View File

@@ -50,6 +50,26 @@ describe User do
end end
end end
describe 'OmniAuth' do
describe '#email_provided?' do
it "is false if the email matchs was temporarely assigned by the OmniAuth process" do
subject.email = 'omniauth@participacion-ABCD-twitter.com'
expect(subject.email_provided?).to eq(false)
end
it "is true if the email is not omniauth-like" do
subject.email = 'manuelacarmena@example.com'
expect(subject.email_provided?).to eq(true)
end
it "is true if the user's real email is pending to be confirmed" do
subject.email = 'omniauth@participacion-ABCD-twitter.com'
subject.unconfirmed_email = 'manuelacarmena@example.com'
expect(subject.email_provided?).to eq(true)
end
end
end
describe "administrator?" do describe "administrator?" do
it "is false when the user is not an admin" do it "is false when the user is not an admin" do
expect(subject.administrator?).to be false expect(subject.administrator?).to be false

View File

@@ -22,3 +22,5 @@ RSpec.configure do |config|
end end
Capybara.javascript_driver = :poltergeist Capybara.javascript_driver = :poltergeist
OmniAuth.config.test_mode = true

View File

@@ -77,4 +77,7 @@ module CommonActions
/\d errors? prohibited this (.*) from being saved:/ /\d errors? prohibited this (.*) from being saved:/
end end
def expect_to_be_signed_in
expect(find('.top-bar')).to have_content 'My account'
end
end end