--- title: 'Desafío 2 - Mecanismos de autenticación y control de accesos en una aplicación web' tags: Desarrollo de aplicaciones con Ruby on Rails, evaluado --- {%hackmd hackmd-dark-theme %} ## Requerimiento 1: Set-up inicial del proyecto sus relaciones y modelos Creación del proyecto: ```bash rails new the_rial_news -d postgresql ``` Modificar datos de conexión a través de credenciales: **bash** ```bash EDITOR="code --wait" rails credentials:edit ``` **CMD** ```bash set EDITOR=code --wait && rails credentials:edit ``` ```yml= db: username: user_db password: password_db ``` En el archivo database.yml: ```yml=17 default: &default adapter: postgresql encoding: unicode username: <%= Rails.application.credentials.db[:username] %> password: <%= Rails.application.credentials.db[:password] %> ``` Crear la base de datos: ```bash rails db:create ``` Entidades y relaciones Visto desde la perspectiva de comentarios ```mermaid %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#fff', 'primaryColor': '#FFB439', 'primaryTextColor': '#222', 'primaryBorderColor': '#FFB439', 'tertiaryColor': '#fff' }, 'themeCSS': '.er.attributeBoxEven { fill:#ddd} .er.relationshipLine { stroke:#ccc}' } }%% erDiagram COMMENT ||--|{ USER :escribe COMMENT ||--|{ PUBLICATION :esta ``` Visto desde la perspectiva de noticias y usuarios ```mermaid %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#fff', 'primaryColor': '#FFB439', 'primaryTextColor': '#222', 'primaryBorderColor': '#FFB439', 'tertiaryColor': '#fff' }, 'themeCSS': '.er.attributeBoxEven { fill:#ddd} .er.relationshipLine { stroke:#ccc}' } }%% erDiagram USER ||--|{ COMMENT :escribe ADMIN ||--|{ PUBLICATION :publica COMMENT ||--|{ PUBLICATION :esta ``` Añadir Devise al proyecto: ```bash bundle add devise ``` Instalar el generador de Devise: ```bash rails g devise:install ``` Establecer las noticias como el root path: ```ruby Rails.application.routes.draw do devise_for :users root "publications#index" end ``` Crear el modelo de usuario con el generador de Devise: ```bash rails g devise User name ``` Generar un scaffold para el modelo Publiciones (*noticias*): ```bash rails g scaffold Publication user:references title description:text ``` Generar un modelo para comentarios: ```bash rails g model Comment users:references publications:references content:text ``` Generamos los controladores para comentarios: ```bash rails g controller Comments ``` Arreglamos las relaciones ```ruby= class Publication < ApplicationRecord belongs_to :user has_many :comments end ``` ```ruby class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable enum :role, [:normal_user, :admin] has_many :publications has_many :comments end ``` Y también arreglamos las rutas para anidarla en las noticias: ```ruby Rails.application.routes.draw do devise_for :users resource :puublications do resource :comments, only: [:create] end root "publications#index" end ``` Ejecutar la migración: ```bash rails db:migrate ``` Generar las vistas de devise: ```bash rails g devise:views ``` --- ## Requerimiento 2 *el usuario que no se haya registrado o iniciado sesión solo podrá ver las publicaciones e ingresar a su detalle. (Los usuarios registrados podrán dejar comentarios en las noticias, que podrán ser eliminados por parte de los administradores si no lo consideran apropiados)* ```ruby before_action :authenticate_user!, except: %i[ index show ] ``` Agregar roles a los usuarios con sus permisos. ```bash rails g migration addRoleToUsers role:integer ``` Se genera la siguiente migración, le añadimos un default para que al momento de crear un usuario, tenga el mínimo de permisos: ```ruby= class AddRoleToUsers < ActiveRecord::Migration[7.0] def change add_column :users, :role, :integer end end ``` Ejecutamos la migración: ```bash rails db:migrate ``` Por consecuencia, los usuarios que ya existian se le asignara el role=0: ``` -[ RECORD 1 ]----------+------------------------------- id | 2 email | existente911@gmail.com encrypted_password | $2a$12$iBONMTcrOsgjCFVybYZR... reset_password_token | 4d017a6df9e1641ba2852609cd6... reset_password_sent_at | 2023-06-09 21:44:57.64205 remember_created_at | created_at | 2023-06-09 19:05:49.842948 updated_at | 2023-06-09 21:44:57.64205 role | 0 ``` Usando el método **enum** creamos los roles: ```ruby enum :role, [:normal_user, :admin] ``` Creamos el método para verificar el tipo de usuario: ```ruby class ApplicationController < ActionController::Base def authorized_request(kind = nil) unless kind.include?(current_user.role) redirect_to publications_path, notice: "Solo el administrador puede realizar esta acción" end end end ``` Y ahora vamos al controlador de noticias (*publicaciones*) para utilizar este método: ```ruby before_action only: %i[ new create edit update destroy ] do authorize_request(["admin"]) end ``` ## Requerimiento 3 Si un usuario olvida su contraseña, puede recibir un email para restablecerla. **Configurar GMAIL para devise:** {%speakerdeck mach911/contrasena-para-aplicaciones-gmail %} agregamos lo siguiente en el archivo `config/environments/development.rb`: ```ruby=40 config.action_mailer.raise_delivery_errors = true config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } config.action_mailer.perform_caching = false config.action_mailer.delivery_method = :sendmail config.action_mailer.perform_deliveries = true config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, domain: 'example.com', user_name: '[Gmail Acc]', password: '[Gmail password]', authentication: 'plain', enable_starttls_auto: true } ``` ## Requerimiento 4 Las noticias (publicaciones) deben tener sus tests funcionales. **Setup**: ```ruby include Devise::Test::IntegrationHelpers setup do @publication = publications(:one) @user = users(:one) end ``` Test para la acción **index** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_get_index ``` ```ruby test "should get index" do get publications_url assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Test para la acción **new** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_get_new ``` ```ruby test "should get new" do sign_in users(:one) get new_publication_url assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Test para la acción **create** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_create_publication ``` ```ruby test "should get new" do sign_in @admin get new_publication_url assert_response :success assert_equal 302, response.status assert_equal "utf-8", response.charset end ``` Test para la acción **show** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_show_publication ``` ```ruby test "should show publication" do get publication_url(@publication) assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Test para la acción **edit** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_get_edit ``` ```ruby test "should get edit" do sign_in @admin get edit_publication_url(@publication) assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Test para la acción **update** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_update_publication ``` ```ruby test "should update publication" do sign_in @admin patch publication_url(@publication), params: { publication: { description: @publication.description, title: @publication.title, user_id: @publication.user_id } } assert_redirected_to publication_url(@publication) end ``` Test para la acción **destroy** de noticias: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_destroy_publication ``` ```ruby test "should destroy publication" do sign_in @admin assert_difference("Publication.count", -1) do delete publication_url(@publication) end assert_redirected_to publications_url end ``` ## Requerimiento 5 Hacer deploy del proyecto. Deploy: