--- 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 %} <style> .markdown-body h2 { border-bottom: none; color: #ff7; } </style> ## Contexto Marcelo se encuentra con poca carga laboral, desde que los terremotos ya no se repiten con tanta frecuencia, nadie lo llama para hablar de ellos. Por esto, se ha decidido a dedicar todo su tiempo libre en su verdadera pasión, los gatos. Marcelo se ha acercado a nosotros con la finalidad de crear una aplicación para todos los locos por los gatos como él, llamada **Crazy4Cats**. Marcelo busca poder publicar sus aventuras con sus gatos y espera que todos sus usuarios puedan hacer lo mismo, podrán dar Me gusta o no me gusta a cada publicación que se suba a la página web. Los comentarios pueden ser anónimos como hechos por un usuario. Por último, solicita poder entrar el de cualquier ubicación, dado que viaja mucho por su trabajo para poder revisar avances de la primera entrega. ### Resumen - Los usuarios pueden publicar aventurar - Los comentarios pueden ser anónimos o hechos por usuario - Los usuarios pueden reaccionar a las publicaciones ## Requerimiento 1: Set-up inicial del proyecto sus relaciones y modelos Creación del proyecto: ```bash rails new crazy4cats -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] %> pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> ``` Crear la base de datos: ```bash rails db:create ``` Identificar entidades: ```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 ||--|{ REACTION :realiza REACTION }|--|| PUBLICATION :tiene COMMENT ||--|{ PUBLICATION :esta USER { int id string email string password } REACTION { int user_id int publication_id string kind } PUBLICATION { int id string title text content string image } COMMENT { int user_id FK int publication_id FK text content } ``` 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 image content:text ``` Generar un modelo para comentarios: ```bash rails g model Comment user:references publication:references content:text ``` Para que se le permita comentar a un usuario anónimo, hacemos la modificación en la migración para permitir el campo `user_id` acepte valores `null`: ```ruby class CreateComments < ActiveRecord::Migration[7.0] def change create_table :comments do |t| t.references :user, null: true, foreign_key: true t.references :publication, null: false, foreign_key: true t.text :content t.timestamps end end end ``` Generamos los controladores para comentarios: ```bash rails g controller Comments ``` Generamos un modelo para las reacciones: ```bash rails g model Reaction publication:references user:references kind ``` 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.