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