diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 94b128e4c..37fc2291c 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -4,6 +4,7 @@ // 02. Sidebar // 03. List elements // 04. Stats +// 05. Management // // 01. Global styles @@ -272,3 +273,37 @@ body.admin { } } } + +// 05. Management +// - - - - - - - - - - - - - - - - - - - - - - - - - + +.postfix { + height: rem-calc(48); + line-height: rem-calc(48); +} + +.user-permissions { + + ul { + list-style-type: none; + margin-left: 0; + + li { + font-size: rem-calc(14); + margin-bottom: rem-calc(12); + + span { + color: $text-medium; + font-size: rem-calc(12); + } + + .icon-check { + color: $check; + } + + .icon-x { + color: $delete; + } + } + } +} diff --git a/app/controllers/management/document_verifications_controller.rb b/app/controllers/management/document_verifications_controller.rb new file mode 100644 index 000000000..17ae484d9 --- /dev/null +++ b/app/controllers/management/document_verifications_controller.rb @@ -0,0 +1,41 @@ +class Management::DocumentVerificationsController < Management::BaseController + + def index + @document_verification = Verification::Management::Document.new() + end + + def check + @document_verification = Verification::Management::Document.new(document_verification_params) + + if @document_verification.valid? + if @document_verification.verified? + render :verified + elsif @document_verification.user? + render :new + elsif @document_verification.in_census? + redirect_to new_management_email_verification_path(email_verification: document_verification_params) + else + render :invalid_document + end + else + render :index + end + end + + def create + @document_verification = Verification::Management::Document.new(document_verification_params) + @document_verification.verify + render :verified + end + + private + + def document_verification_params + params.require(:document_verification).permit(:document_type, :document_number) + end + + +end + + + diff --git a/app/controllers/management/email_verifications_controller.rb b/app/controllers/management/email_verifications_controller.rb new file mode 100644 index 000000000..182fac2e2 --- /dev/null +++ b/app/controllers/management/email_verifications_controller.rb @@ -0,0 +1,24 @@ +class Management::EmailVerificationsController < Management::BaseController + + def new + @email_verification = Verification::Management::Email.new(email_verification_params) + end + + def create + @email_verification = Verification::Management::Email.new(email_verification_params) + + if @email_verification.save + render :sent + else + render :new + end + end + + private + + def email_verification_params + params.require(:email_verification).permit(:document_type, :document_number, :email) + end + +end + diff --git a/app/controllers/management/users_controller.rb b/app/controllers/management/users_controller.rb new file mode 100644 index 000000000..bb6766c70 --- /dev/null +++ b/app/controllers/management/users_controller.rb @@ -0,0 +1,25 @@ +class Management::UsersController < Management::BaseController + def new + @user = User.new(user_params) + end + + def create + @user = User.new(user_params) + @user.skip_password_validation = true + @user.terms_of_service = '1' + @user.residence_verified_at = Time.now + @user.verified_at = Time.now + + if @user.save then + render :show + else + render :new + end + end + + private + + def user_params + params.require(:user).permit(:document_type, :document_number, :username, :email) + end +end diff --git a/app/controllers/users/confirmations_controller.rb b/app/controllers/users/confirmations_controller.rb new file mode 100644 index 000000000..78574cfd9 --- /dev/null +++ b/app/controllers/users/confirmations_controller.rb @@ -0,0 +1,51 @@ +class Users::ConfirmationsController < Devise::ConfirmationsController + + # new action, PATCH does not exist in the default Devise::ConfirmationsController + # PATCH /resource/confirmation + def update + self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token]) + + if resource.encrypted_password.blank? + resource.assign_attributes(resource_params) + + if resource.valid? # password is set correctly + resource.save + resource.confirm + set_flash_message(:notice, :confirmed) if is_flashing_format? + sign_in_and_redirect(resource_name, resource) + else + render :show + end + else + resource.errors.add(:email, :password_already_set) + respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new } + end + end + + # GET /resource/confirmation?confirmation_token=abcdef + def show + # In the default implementation, this already confirms the resource: + # self.resource = self.resource = resource_class.confirm_by_token(params[:confirmation_token]) + self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token]) + + yield resource if block_given? + + # New condition added to if: when no password was given, display the "show" view (which uses "update" above) + if resource.encrypted_password.blank? + respond_with_navigational(resource){ render :show } + elsif resource.errors.empty? + resource.confirm # Last change: confirm happens here for people with passwords instead of af the top of the show action + set_flash_message(:notice, :confirmed) if is_flashing_format? + respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) } + else + respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new } + end + end + + protected + + def resource_params + params.require(resource_name).permit(:password, :password_confirmation, :email) + end + +end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index c93b61c80..fe914404f 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -1,11 +1,18 @@ class Users::SessionsController < Devise::SessionsController def after_sign_in_path_for(resource) - if resource.show_welcome_screen? + if !verifying_via_email? && resource.show_welcome_screen? welcome_path else - root_path + super end end + private + + def verifying_via_email? + stored_path = session[stored_location_key_for(resource)] || "" + stored_path[0..5] == "/email" + end + end diff --git a/app/controllers/verification/email_controller.rb b/app/controllers/verification/email_controller.rb index 017269095..d9172a239 100644 --- a/app/controllers/verification/email_controller.rb +++ b/app/controllers/verification/email_controller.rb @@ -6,7 +6,11 @@ class Verification::EmailController < ApplicationController def show if Verification::Email.find(current_user, params[:email_verification_token]) - current_user.update(verified_at: Time.now) + + current_user.update(verified_at: Time.now, + document_number: current_user.document_number || current_user.unconfirmed_document_number, + residence_verified_at: current_user.residence_verified_at || Time.now) + redirect_to account_path, notice: t('verification.email.show.flash.success') else redirect_to verified_user_path, alert: t('verification.email.show.alert.failure') @@ -17,7 +21,11 @@ class Verification::EmailController < ApplicationController @email = Verification::Email.new(@verified_user) if @email.save current_user.reload - Mailer.email_verification(current_user, @email.recipient, @email.encrypted_token).deliver_later + Mailer.email_verification(current_user, + @email.recipient, + @email.encrypted_token, + @verified_user.document_type, + @verified_user.document_number).deliver_later redirect_to account_path, notice: t('verification.email.create.flash.success', email: @verified_user.email) else redirect_to verified_user_path, alert: t('verification.email.create.alert.failure') @@ -33,4 +41,4 @@ class Verification::EmailController < ApplicationController def verified_user_params params.require(:verified_user).permit(:id) end -end \ No newline at end of file +end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 1c72fedbf..faf9b2302 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -12,21 +12,10 @@ module AdminHelper options end - def humanize_document_type(document_type) - case document_type - when "1" - t "verification.residence.new.document_type.spanish_id" - when "2" - t "verification.residence.new.document_type.passport" - when "3" - t "verification.residence.new.document_type.residence_card" - end - end - private def namespace controller.class.parent.name.downcase end -end \ No newline at end of file +end diff --git a/app/helpers/user_helper.rb b/app/helpers/user_helper.rb new file mode 100644 index 000000000..9fe2a801d --- /dev/null +++ b/app/helpers/user_helper.rb @@ -0,0 +1,12 @@ +module UserHelper + def humanize_document_type(document_type) + case document_type + when "1" + t "verification.residence.new.document_type.spanish_id" + when "2" + t "verification.residence.new.document_type.passport" + when "3" + t "verification.residence.new.document_type.residence_card" + end + end +end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index 8ce28060c..bf646019a 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -1,6 +1,7 @@ class Mailer < ApplicationMailer helper :text_with_links helper :mailer + helper :user def comment(comment) @comment = comment @@ -16,10 +17,12 @@ class Mailer < ApplicationMailer mail(to: @recipient.email, subject: t('mailers.reply.subject')) if @commentable.present? && @recipient.present? end - def email_verification(user, recipient, token) + def email_verification(user, recipient, token, document_type, document_number) @user = user @recipient = recipient @token = token + @document_type = document_type + @document_number = document_number mail(to: @recipient, subject: t('mailers.email_verification.subject')) end diff --git a/app/models/user.rb b/app/models/user.rb index dd8c725be..f652a8f9c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -25,6 +25,7 @@ class User < ActiveRecord::Base validates :username, presence: true, unless: :organization? validates :username, uniqueness: true, unless: :organization? + validates :document_number, uniqueness: { scope: :document_type }, allow_nil: true validate :validate_username_length validates :official_level, inclusion: {in: 0..5} @@ -35,11 +36,16 @@ class User < ActiveRecord::Base accepts_nested_attributes_for :organization, update_only: true + attr_accessor :skip_password_validation + scope :administrators, -> { joins(:administrators) } scope :moderators, -> { joins(:moderator) } scope :organizations, -> { joins(:organization) } scope :officials, -> { where("official_level > 0") } scope :for_render, -> { includes(:organization) } + scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) } + + before_validation :clean_document_number def self.find_for_oauth(auth, signed_in_resource = nil) # Get the identity and user if they exist @@ -158,11 +164,20 @@ class User < ActiveRecord::Base end def show_welcome_screen? - sign_in_count == 1 && unverified? && !organization + sign_in_count == 1 && unverified? && !organization && !administrator? + end + + def password_required? + return false if skip_password_validation + super end private + def clean_document_number + self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank? + end + def validate_username_length validator = ActiveModel::Validations::LengthValidator.new( attributes: :username, diff --git a/app/models/verification/management/document.rb b/app/models/verification/management/document.rb new file mode 100644 index 000000000..6c9a8897f --- /dev/null +++ b/app/models/verification/management/document.rb @@ -0,0 +1,34 @@ +class Verification::Management::Document + include ActiveModel::Model + + attr_accessor :document_type + attr_accessor :document_number + + validates :document_type, :document_number, presence: true + + delegate :username, :email, to: :user, allow_nil: true + + def user + @user = User.by_document(document_type, document_number).first + end + + def user? + user.present? + end + + def in_census? + CensusApi.new.call(document_type, document_number).valid? + end + + def verified? + user? && user.level_three_verified? + end + + def verify + user.update(verified_at: Time.now) if user? + end + +end + + + diff --git a/app/models/verification/management/email.rb b/app/models/verification/management/email.rb new file mode 100644 index 000000000..bb284ea2b --- /dev/null +++ b/app/models/verification/management/email.rb @@ -0,0 +1,61 @@ +class Verification::Management::Email + include ActiveModel::Model + + attr_accessor :document_type + attr_accessor :document_number + attr_accessor :email + + validates :document_type, :document_number, :email, presence: true + validates :email, format: { with: Devise.email_regexp }, allow_blank: true + validate :validate_user + validate :validate_document_number + + delegate :username, to: :user, allow_nil: true + + def user + @user ||= User.where(email: email).first + end + + def user? + user.present? + end + + def save + return false unless valid? + + plain_token, encrypted_token = Devise.token_generator.generate(User, :email_verification_token) + + user.update(document_type: document_type, + unconfirmed_document_number: document_number, + email_verification_token: plain_token) + + Mailer.email_verification(user, email, encrypted_token, document_type, document_number).deliver_later + true + end + + private + + def validate_user + return if errors.count > 0 + if !user? + errors.add(:email, I18n.t('errors.messages.user_not_found')) + elsif user.level_three_verified? + errors.add(:email, I18n.t('management.email_verifications.already_verified')) + end + end + + def validate_document_number + return if errors.count > 0 + if document_number_mismatch? + errors.add(:email, + I18n.t('management.email_verifications.document_mismatch', + document_type: ApplicationController.helpers.humanize_document_type(user.document_type), + document_number: user.document_number)) + end + end + + def document_number_mismatch? + user? && user.document_number.present? && + (user.document_number != document_number || user.document_type != document_type) + end +end diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb index 722609134..f552272e2 100644 --- a/app/views/devise/confirmations/new.html.erb +++ b/app/views/devise/confirmations/new.html.erb @@ -1,16 +1,23 @@ -<% provide :title do %><%= t("devise_views.confirmations.title") %><% end %> -
<%= f.password_field :password %>
+<%= f.password_field :password_confirmation %>
+ <% end %> + + <%= hidden_field_tag :confirmation_token,@confirmation_token %> +<%= t('devise_views.confirmations.show.instructions_html', email: resource.email) %>
+ +<%= form_for(resource, + as: resource_name, + url: update_user_confirmation_path, + html: { method: :patch }) do |f| %> + +<%= t('devise_views.confirmations.show.please_set_password') %>
+ + <%= render 'shared/errors', resource: resource %> + ++ <%= t("mailers.email_verification.instructions_2_html", + document_type: humanize_document_type(@document_type), + document_number: @document_number) %> +
<%= t("mailers.email_verification.thanks") %>
diff --git a/app/views/management/_account_info.html.erb b/app/views/management/_account_info.html.erb new file mode 100644 index 000000000..972398e89 --- /dev/null +++ b/app/views/management/_account_info.html.erb @@ -0,0 +1,10 @@ ++ <%= t("management.document_type_label") %> <%= humanize_document_type(account.document_type) %> + <%= t("management.document_number") %> <%= account.document_number %> + <% if account.username.present? %> + <%= t("management.username_label") %> <%= account.username %> + <% end %> + <% if account.email.present? %> + <%= t("management.email_label") %> <%= account.email %> + <% end %> +
diff --git a/app/views/management/_menu.html.erb b/app/views/management/_menu.html.erb index e69de29bb..6493f8a66 100644 --- a/app/views/management/_menu.html.erb +++ b/app/views/management/_menu.html.erb @@ -0,0 +1,16 @@ + diff --git a/app/views/management/_user_permissions.html.erb b/app/views/management/_user_permissions.html.erb new file mode 100644 index 000000000..8b17605ea --- /dev/null +++ b/app/views/management/_user_permissions.html.erb @@ -0,0 +1,20 @@ +<% + # Parameters: + # message: A string explaining the permissions + # permissions: An array of symbols containing the permissions + # (can be :debates, :proposal, :support_proposal, :votes) +%> + diff --git a/app/views/management/document_verifications/index.html.erb b/app/views/management/document_verifications/index.html.erb new file mode 100644 index 000000000..20d7724b4 --- /dev/null +++ b/app/views/management/document_verifications/index.html.erb @@ -0,0 +1,25 @@ ++ <%= t("management.document_verifications.has_no_account_html", + link: link_to('http://decide.madrid.es', 'http://decide.madrid.es'), + target: "_blank") %> +
diff --git a/app/views/management/document_verifications/new.html.erb b/app/views/management/document_verifications/new.html.erb new file mode 100644 index 000000000..be2d3a1c5 --- /dev/null +++ b/app/views/management/document_verifications/new.html.erb @@ -0,0 +1,17 @@ +<%= render 'management/account_info.html', account: @document_verification %> + +<%= t("management.email_verifications.introduce_email") %>
+ + <%= form_for @email_verification, + as: :email_verification, + url: management_email_verifications_path do |f| %> + <%= f.hidden_field :document_type %> + <%= f.hidden_field :document_number %> + <%= f.text_field :email, label: false, placeholder: t('management.email_verifications.email_placeholder') %> + + <%= f.submit t("management.email_verifications.send_email"), class: "button success radius" %> + <% end %> ++ <%= link_to t('management.users.create_user'), new_management_user_path(user: params[:email_verification]), class: "button warning radius" %> +
++ <%= t("management.print_info") %> +
+ diff --git a/app/views/management/users/new.html.erb b/app/views/management/users/new.html.erb new file mode 100644 index 000000000..1188b7872 --- /dev/null +++ b/app/views/management/users/new.html.erb @@ -0,0 +1,25 @@ +<%= render 'management/account_info.html', account: @user %> + +<%= t("management.users.create_user_info") %>
+ +<%= render 'management/user_permissions', + message: t("management.document_verifications.in_census_has_following_permissions"), + permissions: [:debates, :create_proposals, :support_proposals, :vote_proposals] %> + +<%= t("management.users.create_user_success_html", email: @user.email) %>
+ +<%= render 'management/user_permissions', + message: t("management.document_verifications.in_census_has_following_permissions"), + permissions: [:debates, :create_proposals, :support_proposals, :vote_proposals] %> diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 303bd5ded..06cb48980 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -1,5 +1,11 @@ en: activerecord: + errors: + models: + user: + attributes: + email: + password_already_set: "This user already has a password" models: activity: Activity comment: Comment diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index 3ba5e7cca..89fba981c 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -1,5 +1,11 @@ es: activerecord: + errors: + models: + user: + attributes: + email: + password_already_set: "Este usuario ya tiene una clave asociada" models: activity: one: actividad diff --git a/config/locales/devise_views.en.yml b/config/locales/devise_views.en.yml index 39136e18a..2c04d3ce8 100644 --- a/config/locales/devise_views.en.yml +++ b/config/locales/devise_views.en.yml @@ -1,9 +1,17 @@ en: devise_views: confirmations: - title: "Resend confirmation instructions" - email_label: Email - submit: "Resend confirmation" + new: + title: "Resend confirmation instructions" + email_label: Email + submit: "Resend confirmation" + show: + title: "Confirm my account" + instructions_html: "Confirming the account with email %{email}" + please_set_password: "Please choose your new pasword (it will allow you to login with the email above)" + new_password_label: "New access password" + new_password_confirmation_label: "Repeat access password" + submit: "Confirm" mailer: confirmation_instructions: title: "Welcome to open government portal" diff --git a/config/locales/devise_views.es.yml b/config/locales/devise_views.es.yml index 54e23911c..f2ca13215 100644 --- a/config/locales/devise_views.es.yml +++ b/config/locales/devise_views.es.yml @@ -1,9 +1,17 @@ es: devise_views: confirmations: - title: "Reenviar instrucciones de confirmación" - email_label: Email - submit: "Reenviar instrucciones" + new: + title: "Reenviar instrucciones de confirmación" + email_label: Email + submit: "Reenviar instrucciones" + show: + title: "Confirmar mi cuenta" + instructions_html: "Vamos a proceder a confirmar la cuenta con el email %{email}" + please_set_password: "Por favor introduce una nueva clave de acceso para su cuenta (te permitirá hacer login con el email de más arriba)" + new_password_label: "Nueva clave de acceso" + new_password_confirmation_label: "Repite la clave de nuevo" + submit: "Confirmar" mailer: confirmation_instructions: title: "Te damos la bienvenida al Portal de Gobierno Abierto del Ayuntamiento de Madrid" diff --git a/config/locales/en.yml b/config/locales/en.yml index 67a805059..c71652799 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,5 +1,8 @@ en: locale: English + errors: + messages: + user_not_found: "User not found" layouts: header: external_link_transparency: Transparency diff --git a/config/locales/es.yml b/config/locales/es.yml index ae1e57b0a..9280f83db 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1,5 +1,8 @@ es: locale: "Español" + errors: + messages: + user_not_found: "No se encontró el usuario" layouts: header: external_link_transparency: Transparencia diff --git a/config/locales/mailers.en.yml b/config/locales/mailers.en.yml index 030dc13de..50330a69f 100644 --- a/config/locales/mailers.en.yml +++ b/config/locales/mailers.en.yml @@ -15,5 +15,6 @@ en: title: Please verify yourself instructions_html: "We need to verify you using this email, which we got from the Census. %{verification_link}" click_here_to_verify: "Please click here to verify yourself" + instructions_2_html: "This email will verify your account with %{document_type} %{document_number}. If these don't belong to you, please don't click on the previous link and ignore this email." thanks: "Thanks" diff --git a/config/locales/mailers.es.yml b/config/locales/mailers.es.yml index 1ca753ab5..bf8b276e1 100644 --- a/config/locales/mailers.es.yml +++ b/config/locales/mailers.es.yml @@ -15,4 +15,5 @@ es: title: Verifica tu cuenta con el siguiente enlace instructions_html: "Para terminar de verificar tu cuenta de usuario en el Portal de Gobierno Abierto del Ayuntamiento de Madrid, necesitamos que pulses %{verification_link}." click_here_to_verify: "en este enlace" + instructions_2_html: "Este email es para verificar tu cuenta con %{document_type} %{document_number}. Si esos no son tus datos, por favor no pulses el enlace anterior e ignora este email." thanks: "Muchas gracias." diff --git a/config/locales/management.en.yml b/config/locales/management.en.yml index 473fb47a5..d9ea82a46 100644 --- a/config/locales/management.en.yml +++ b/config/locales/management.en.yml @@ -1,10 +1,51 @@ en: management: + print_info: "Print this info" + username_label: "User name" + email_label: "Email" + check: "Check" + document_number: "Document number" + document_type_label: "Document type" + menu: + title: "Management" + users: "Users" dashboard: index: - title: Management Dashboard + title: "Management" proposals: print: print_button: Print alert: - unverified_user: User is not verified \ No newline at end of file + unverified_user: User is not verified + permissions: + debates: "Engage in debates" + create_proposals: "Create proposals" + support_proposals: "Support proposals" + vote_proposals: "Vote proposals" + document_verifications: + title: "User management" + please_check_account_data: "Please check that the account data above are correct." + already_verified: "This user account is already verified." + in_census_has_following_permissions: "This user can participate in the website with the following permissions:" + not_in_census: "This document is not registered in Madrid." + not_in_census_info: "Citizens not in the Census can participate in the website with the following permissions:" + has_no_account_html: "In order to create an account, go to %{link} and click in 'Register' in the upper-left part of the screen." + verify: "Verify" + email_verifications: + document_found_in_census: "This document was found in the census, but it has no user account associated to it. Please choose one of the following options:" + if_existing_account: "If the person has already a user account created in the website," + introduce_email: "Please introduce the email used on the account:" + email_placeholder: "Write the email this person used to create his or her account" + send_email: "Send verification email" + email_sent_instructions: "In order to completely verify this user, it is necessary that the user clicks on a link which we have sent to the email address above. This step is needed in order to confirm that the address belongs to him." + if_no_existing_account: "If this person has not created an account yet" + document_mismatch: "This email belongs to a user which already has an associated id: %{document_number}(%{document_type})" + already_verified: "This user account is already verified." + users: + create_user: "Create a new account" + create_user_info: "We will create an account with the following data:" + create_user_submit: "Create user" + create_user_success_html: + "We have sent an email to the email address %{email} in order to verify that it belongs to this user. + It contains a link they have to click. Then they will have to set their access password before being able + to log in to the website" diff --git a/config/locales/management.es.yml b/config/locales/management.es.yml index 937993c01..913098e36 100644 --- a/config/locales/management.es.yml +++ b/config/locales/management.es.yml @@ -1,10 +1,51 @@ es: management: + print_info: "Imprimir esta información" + username_label: "Nombre de usuario" + email_label: "Email" + check: "Comprobar" + document_number: "Número de documento" + document_type_label: "Tipo de documento" + menu: + title: "Gestión" + users: "Usuarios" dashboard: index: - title: Gestión + title: "Gestión" proposals: print: print_button: Imprimir alert: - unverified_user: Este usuario no está verificado \ No newline at end of file + unverified_user: Este usuario no está verificado + permissions: + debates: "Participar en debates" + create_proposals: "Crear nuevas propuestas" + support_proposals: "Apoyar propuestas" + vote_proposals: "Participar en las votaciones finales" + document_verifications: + title: "Gestión de usuarios" + please_check_account_data: "Compruebe que los datos anteriores son correctos para proceder a verificar la cuenta completamente." + already_verified: "Esta cuenta de usuario ya está verificada." + in_census_has_following_permissions: "Este usuario puede participar en el Portal de Gobierno Abierto del Ayuntamiento de Madrid con las siguientes posibilidades:" + not_in_census: "Este documento no está registrado en el Padrón Municipal de Madrid." + not_in_census_info: "Las personas no empadronadas en Madrid pueden participar en el Portal de Gobierno Abierto del Ayuntamiento de Madrid con las siguientes posibilidades:" + has_no_account_html: "Para crear un usuario entre en %{link} y haga clic en la opción 'Registrarse' en la parte superior derecha de la pantalla." + verify: "Verificar usuario" + email_verifications: + document_found_in_census: "Este documento está en el registro del padrón municipal, pero todavía no tiene una cuenta de usuario asociada. Elige una de las opciones siguientes:" + if_existing_account: "Si la persona ya ha creado una cuenta de usuario en la web" + introduce_email: "Introduce el email con el que creó la cuenta:" + email_placeholder: "Introduce el email de registro" + send_email: "Enviar email de verificación" + if_no_existing_account: "Si la persona todavía no ha creado una cuenta de usuario en la web" + email_sent_instructions: "Para terminar de verificar esta cuenta es necesario que haga clic en el enlace que le hemos enviado a la dirección de correo que figura arriba. Este paso es necesario para confirmar que dicha cuenta de usuario es suya." + document_mismatch: "Ese email corresponde a un usuario que ya tiene asociado el documento %{document_number}(%{document_type})" + already_verified: "Esta cuenta de usuario ya está verificada." + users: + create_user: "Crear nueva cuenta de usuario" + create_user_info: "Procedemos a crear un usuario con la siguiente información:" + create_user_submit: "Crear usuario" + create_user_success_html: + "Hemos enviado un correo electrónico a %{email} para verificar que es suya. + El correo enviado contiene un link que el usuario deberá pulsar. Entonces podrá seleccionar + una clave de acceso, y entrar en la web de participación." diff --git a/config/routes.rb b/config/routes.rb index b3a2a5476..ed91d1f07 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,13 @@ Rails.application.routes.draw do + + as :user do + match '/user/confirmation' => 'users/confirmations#update', :via => :patch, :as => :update_user_confirmation + end + devise_for :users, controllers: { registrations: 'users/registrations', sessions: 'users/sessions', + confirmations: 'users/confirmations', omniauth_callbacks: 'users/omniauth_callbacks' } devise_for :organizations, class_name: 'User', @@ -166,6 +172,16 @@ Rails.application.routes.draw do namespace :management do root to: "dashboard#index" + resources :document_verifications, only: [:index, :new, :create] do + collection do + post :check + end + end + + resources :email_verifications, only: [:new, :create] + + resources :users, only: [:new, :create] + get 'sign_in', to: 'sessions#create' resources :sessions, only: :create diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 1a809d905..ab17701c5 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -50,9 +50,10 @@ end (1..40).each do |i| user = create_user("user#{i}@madrid.es") level = [1,2,3].sample - if level == 2 then - user.update(residence_verified_at: Time.now, confirmed_phone: Faker::PhoneNumber.phone_number, document_number: Faker::Number.number(10) ) - elsif level == 3 then + if level >= 2 then + user.update(residence_verified_at: Time.now, confirmed_phone: Faker::PhoneNumber.phone_number, document_number: Faker::Number.number(10), document_type: "1" ) + end + if level == 3 then user.update(verified_at: Time.now, document_number: Faker::Number.number(10) ) end end diff --git a/db/migrate/20151002144206_add_unconfirmed_document_number_to_user.rb b/db/migrate/20151002144206_add_unconfirmed_document_number_to_user.rb new file mode 100644 index 000000000..7a00acb91 --- /dev/null +++ b/db/migrate/20151002144206_add_unconfirmed_document_number_to_user.rb @@ -0,0 +1,5 @@ +class AddUnconfirmedDocumentNumberToUser < ActiveRecord::Migration + def change + add_column :users, :unconfirmed_document_number, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 97ad0ab97..3a84bc7b6 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: 20150930082311) do +ActiveRecord::Schema.define(version: 20151002144206) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -267,30 +267,30 @@ ActiveRecord::Schema.define(version: 20150930082311) do add_index "tags", ["proposals_count"], name: "index_tags_on_proposals_count", using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "email_on_comment", default: false - t.boolean "email_on_comment_reply", default: false - t.string "phone_number", limit: 30 + t.boolean "email_on_comment", default: false + t.boolean "email_on_comment_reply", default: false + t.string "phone_number", limit: 30 t.string "official_position" - t.integer "official_level", default: 0 + t.integer "official_level", default: 0 t.datetime "hidden_at" t.string "sms_confirmation_code" - t.string "username", limit: 60 + t.string "username", limit: 60 t.string "document_number" t.string "document_type" t.datetime "residence_verified_at" @@ -302,7 +302,8 @@ ActiveRecord::Schema.define(version: 20150930082311) do t.datetime "letter_requested_at" t.datetime "confirmed_hide_at" t.string "letter_verification_code" - t.integer "failed_census_calls_count", default: 0 + t.integer "failed_census_calls_count", default: 0 + t.string "unconfirmed_document_number" end add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree diff --git a/spec/factories.rb b/spec/factories.rb index fac81f1c9..6b665e884 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,8 +1,11 @@ FactoryGirl.define do + sequence(:document_number) { |n| "#{n.to_s.rjust(8, '0')}X" } + factory :user do sequence(:username) { |n| "Manuela#{n}" } sequence(:email) { |n| "manuela#{n}@madrid.es" } + password 'judgmentday' terms_of_service '1' confirmed_at { Time.now } @@ -18,12 +21,14 @@ FactoryGirl.define do unconfirmed_phone "611111111" confirmed_phone "611111111" sms_confirmation_code "1234" - document_number "12345678Z" + document_type "1" + document_number end trait :level_three do verified_at Time.now - document_number "12345678Z" + document_type "1" + document_number end trait :hidden do @@ -49,8 +54,8 @@ FactoryGirl.define do factory :verification_residence, class: Verification::Residence do user - document_number '12345678Z' - document_type 1 + document_number + document_type "1" date_of_birth Date.new(1980, 12, 31) postal_code "28013" terms_of_service '1' @@ -62,7 +67,7 @@ FactoryGirl.define do factory :failed_census_call do user - document_number '11111111A' + document_number document_type 1 date_of_birth Date.new(1900, 1, 1) postal_code '28000' @@ -83,7 +88,7 @@ FactoryGirl.define do end factory :verified_user do - document_number '12345678Z' + document_number document_type 'dni' end diff --git a/spec/features/admin/verifications_spec.rb b/spec/features/admin/verifications_spec.rb index f21fb1607..975d5c3e2 100644 --- a/spec/features/admin/verifications_spec.rb +++ b/spec/features/admin/verifications_spec.rb @@ -44,7 +44,7 @@ feature 'Incomplete verifications' do within "#user_#{incompletely_verified_user.id}" do expect(page).to have_content "Spanish ID" - expect(page).to have_content "11111111A" + expect(page).to have_content incompletely_verified_user.document_number expect(page).to have_content Date.new(1900, 1, 1) expect(page).to have_content "28000" end diff --git a/spec/features/management/document_verifications_spec.rb b/spec/features/management/document_verifications_spec.rb new file mode 100644 index 000000000..50ec9b3db --- /dev/null +++ b/spec/features/management/document_verifications_spec.rb @@ -0,0 +1,56 @@ +require 'rails_helper' + +feature 'DocumentVerifications' do + + background do + login_as_manager(create(:manager)) + end + + scenario 'Verifying a level 3 user shows an "already verified" page' do + user = create(:user, :level_three) + + visit management_document_verifications_path + fill_in 'document_verification_document_number', with: user.document_number + click_button 'Check' + + expect(page).to have_content "already verified" + end + + scenario 'Verifying a level 2 user displays the verification form' do + + user = create(:user, :level_two) + + visit management_document_verifications_path + fill_in 'document_verification_document_number', with: user.document_number + click_button 'Check' + + expect(page).to have_content "Vote proposals" + + click_button 'Verify' + + expect(page).to have_content "already verified" + + expect(user.reload).to be_level_three_verified + end + + scenario 'Verifying a user which does not exist and is not in the census shows an error' do + + expect_any_instance_of(Verification::Management::Document).to receive(:in_census?).and_return(false) + + visit management_document_verifications_path + fill_in 'document_verification_document_number', with: "inexisting" + click_button 'Check' + + expect(page).to have_content "This document is not registered" + end + + scenario 'Verifying a user which does exists in the census but not in the db redirects allows sending an email' do + + visit management_document_verifications_path + fill_in 'document_verification_document_number', with: '1234' + click_button 'Check' + + expect(page).to have_content "Please introduce the email used on the account" + end + +end diff --git a/spec/features/management/email_verifications_spec.rb b/spec/features/management/email_verifications_spec.rb new file mode 100644 index 000000000..0b25ea1f1 --- /dev/null +++ b/spec/features/management/email_verifications_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +feature 'EmailVerifications' do + + scenario 'Verifying a level 1 user via email' do + + login_as_manager(create(:manager)) + + user = create(:user) + + visit management_document_verifications_path + fill_in 'document_verification_document_number', with: '1234' + click_button 'Check' + + expect(page).to have_content "Please introduce the email used on the account" + + fill_in 'email_verification_email', with: user.email + click_button 'Send verification email' + + expect(page).to have_content("In order to completely verify this user, it is necessary that the user clicks on a link") + + user.reload + + login_as(user) + + sent_token = /.*email_verification_token=(.*)".*/.match(ActionMailer::Base.deliveries.last.body.to_s)[1] + visit email_path(email_verification_token: sent_token) + + expect(page).to have_content "You are now a verified user" + + expect(page).to_not have_link "Verify my account" + expect(page).to have_content "Verified account" + + expect(user.reload.document_number).to eq('1234') + expect(user).to be_level_three_verified + + end +end + + + diff --git a/spec/features/management/users_spec.rb b/spec/features/management/users_spec.rb new file mode 100644 index 000000000..5f17ecf7e --- /dev/null +++ b/spec/features/management/users_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' + +feature 'users' do + + scenario 'Creating a level 3 user from scratch' do + + login_as_manager(create(:manager)) + + visit management_document_verifications_path + fill_in 'document_verification_document_number', with: '1234' + click_button 'Check' + + expect(page).to have_content "Please introduce the email used on the account" + + click_link 'Create a new account' + + fill_in 'user_username', with: 'pepe' + fill_in 'user_email', with: 'pepe@gmail.com' + + click_button 'Create user' + + expect(page).to have_content "We have sent an email" + + user = User.find_by_email('pepe@gmail.com') + + expect(user).to be_level_three_verified + expect(user).to be_residence_verified + expect(user).to_not be_confirmed + + 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 "Confirming the account with email" + + fill_in 'user_password', with: '12345678' + fill_in 'user_password_confirmation', with: '12345678' + + click_button 'Confirm' + + expect(user.reload).to be_confirmed + + expect(page).to have_content "Your email address has been successfully confirmed." + end +end + + + diff --git a/spec/features/welcome_spec.rb b/spec/features/welcome_spec.rb index cef26e035..f75eadd0c 100644 --- a/spec/features/welcome_spec.rb +++ b/spec/features/welcome_spec.rb @@ -10,6 +10,24 @@ feature "Welcome screen" do expect(current_path).to eq(welcome_path) end + scenario 'a regular user does not see it when coing to /email' do + + plain, encrypted = Devise.token_generator.generate(User, :email_verification_token) + + user = create(:user, email_verification_token: plain) + + visit email_path(email_verification_token: encrypted) + + fill_in 'user_email', with: user.email + fill_in 'user_password', with: user.password + + click_button 'Log in' + + expect(page).to have_content("You are now a verified user") + + expect(current_path).to eq(account_path) + end + scenario 'it is not shown more than once' do user = create(:user, sign_in_count: 2) @@ -42,4 +60,12 @@ feature "Welcome screen" do expect(current_path).to eq(proposals_path) end + scenario 'is not shown to administrators' do + administrator = create(:administrator) + + login_through_form_as(administrator.user) + + expect(current_path).to eq(proposals_path) + end + end diff --git a/spec/helpers/admin_helper_spec.rb b/spec/helpers/user_helper_spec.rb similarity index 92% rename from spec/helpers/admin_helper_spec.rb rename to spec/helpers/user_helper_spec.rb index 25fd62a8e..bb0df0de5 100644 --- a/spec/helpers/admin_helper_spec.rb +++ b/spec/helpers/user_helper_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe AdminHelper do +describe UserHelper do describe '#humanize_document_type' do it "should return a humanized document type" do @@ -10,4 +10,4 @@ describe AdminHelper do end end -end \ No newline at end of file +end diff --git a/spec/models/residence_spec.rb b/spec/models/residence_spec.rb index 53e8c44e6..5396aacd3 100644 --- a/spec/models/residence_spec.rb +++ b/spec/models/residence_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe Verification::Residence do - let(:residence) { build(:verification_residence) } + let(:residence) { build(:verification_residence, document_number: "12345678Z") } describe "validations" do @@ -57,7 +57,7 @@ describe Verification::Residence do residence.user = user residence.save - residence2 = build(:verification_residence) + build(:verification_residence) residence.valid? expect(residence.errors[:document_number]).to include("has already been taken") @@ -112,7 +112,7 @@ describe Verification::Residence do describe "Failed census call" do it "stores failed census API calls" do - residence = build(:verification_residence, :invalid) + residence = build(:verification_residence, :invalid, document_number: "12345678Z") residence.save expect(FailedCensusCall.count).to eq(1) @@ -126,4 +126,4 @@ describe Verification::Residence do end end -end \ No newline at end of file +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 29b239da9..14f20ad1d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -287,4 +287,19 @@ describe User do end + describe "document_number" do + it "should upcase document number" do + user = User.new({document_number: "x1234567z"}) + user.valid? + expect(user.document_number).to eq("X1234567Z") + end + + it "should remove all characters except numbers and letters" do + user = User.new({document_number: " 12.345.678 - B"}) + user.valid? + expect(user.document_number).to eq("12345678B") + end + + end + end diff --git a/spec/models/verification/management/email_spec.rb b/spec/models/verification/management/email_spec.rb new file mode 100644 index 000000000..d303367bb --- /dev/null +++ b/spec/models/verification/management/email_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +describe Verification::Management::Email do + + describe "#user" do + subject { described_class.new(document_type: "1", document_number: "1234", email: "inexisting@gmail.com") } + it "returns nil/false when the user does not exist" do + expect(subject.user).to be_nil + expect(subject.user?).to_not be + end + end + + describe "validations" do + it "is not valid if the user does not exist" do + expect(described_class.new(document_type: "1", document_number: "1234", email: "inexisting@gmail.com")).to_not be_valid + end + + it "is not valid if the user is already level 3" do + user = create(:user, :level_three) + expect(described_class.new(document_type: "1", document_number: "1234", email: user.email)).to_not be_valid + end + + it "is not valid if the user already has a different document number" do + user = create(:user, document_number: "1234", document_type: "1") + expect(described_class.new(document_type: "1", document_number: "5678", email: user.email)).to_not be_valid + end + end + + describe "#save" do + it "does nothing if not valid" do + expect(described_class.new(document_type: "1", document_number: "1234", email: "inexisting@gmail.com").save).to eq(false) + end + + it "updates the user and sends an email" do + user = create(:user) + validation = described_class.new(document_type: "1", document_number: "1234", email: user.email) + + mail = double(:mail) + + allow(validation).to receive(:user).and_return user + expect(mail).to receive(:deliver_later) + expect(Devise.token_generator).to receive(:generate).with(User, :email_verification_token).and_return(["1","2"]) + expect(user).to receive(:update).with(document_type: "1", unconfirmed_document_number: "1234", email_verification_token: "1") + expect(Mailer).to receive(:email_verification).with(user, user.email, "2", "1", "1234").and_return(mail) + + validation.save + end + end +end diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index 1059b1c2f..f1e47f5c3 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -14,6 +14,10 @@ module CommonActions click_button 'Sign up' end + def login_as_manager(manager) + visit management_sign_in_path(login: manager.username, clave_usuario: manager.password) + end + def login_through_form_as(user) visit root_path click_link 'Log in'