---
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.