enable granular permissions features in all envs

since they're on in production, they need to be turned on
for local development and jenkins to avoid sneaking bugs
into production

I intend to actually _remove_ the feature flags
and associated permission blocks in a subsequent commit

flag=none
closes FOO-3874

Change-Id: I98f7c043ae93cb1c0b4aef46999417af43684e55
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/308815
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: August Thornton <august@instructure.com>
QA-Review: Jeremy Stanley <jeremy@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
Jeremy Stanley 2023-01-13 15:23:18 -07:00
parent 68b621566d
commit 9c0606d316
23 changed files with 63 additions and 249 deletions

View File

@ -97,7 +97,7 @@ class CoursePacing::PacesApiController < ApplicationController
end
def authorize_action
authorized_action(course, @current_user, :manage_content)
authorized_action(course, @current_user, [:manage_content, :manage_course_content_edit])
end
def load_contexts

View File

@ -6,45 +6,30 @@ default_recaptcha_registration_enable:
description: Requires Recaptcha for self-registration by default
applies_to: SiteAdmin
granular_permissions_manage_course_content:
state: hidden
state: allowed_on
applies_to: RootAccount
display_name: Granular permissions for managing course content
description: Add granularity to permissions around managing all other course content
environments:
development:
state: allowed_on
granular_permissions_manage_courses:
state: hidden
state: allowed_on
applies_to: RootAccount
display_name: Granular permissions for managing courses
description: Add granularity to permissions around managing courses i.e. Add / Publish / Conclude / Delete
environments:
development:
state: allowed_on
granular_permissions_manage_users:
state: hidden
state: allowed_on
applies_to: RootAccount
display_name: Granular permissions for Add/Remove roles from Course
description: Adds granular permissions for adding and removing each role, and a separate permission for other course admin actions
environments:
development:
state: allowed_on
granular_permissions_manage_groups:
state: hidden
state: allowed_on
applies_to: RootAccount
display_name: Granular permissions for managing groups in Account or Course contexts
description: Add granularity to permissions around managing groups in Account or Course contexts
environments:
development:
state: allowed_on
granular_permissions_manage_lti:
state: hidden
state: allowed_on
applies_to: RootAccount
display_name: Granular permissions for managing LTI tools in Account or Course contexts
description: Add granularity to permissions around managing LTI tools in Account or Course contexts
environments:
development:
state: allowed_on
course_templates:
state: hidden
applies_to: RootAccount
@ -62,13 +47,10 @@ allow_unconfirmed_users_in_user_list:
display_name: Allow Unconfirmed E-mail Addresses When Adding Users
description: This is technicaly a security vulnerability, but some schools need to preserve their workflows for a while.
granular_permissions_manage_assignments:
state: hidden
state: allowed_on
applies_to: RootAccount
display_name: Granular permissions for assignments and quizzes
description: Separate out granular add and delete permissions for assignments and quizzes from the existing manage permission that controls all three.
environments:
development:
state: allowed_on
course_admin_role_masquerade_permission_check:
state: allowed
applies_to: RootAccount

View File

@ -2111,8 +2111,10 @@ describe CoursesController, type: :request do
@course.enroll_teacher(@user, role: @role).accept!
end
it "cannot update the course without :manage_content" do
Account.default.role_overrides.create!(role: @role, permission: :manage_content, enabled: false)
it "cannot update the course without any manage_content permissions" do
RoleOverride::GRANULAR_MANAGE_COURSE_CONTENT_PERMISSIONS.each do |permission|
Account.default.role_overrides.create!(role: @role, permission:, enabled: false)
end
raw_api_call(:put, @path, @params, @new_values)
@course.reload

View File

@ -1709,7 +1709,7 @@ describe DiscussionTopicsController, type: :request do
@assignment = @topic.context.assignments.build(points_possible: 50)
@topic.assignment = @assignment
@topic.save!
account_admin_user_with_role_changes(role_changes: { manage_assignments: false })
account_admin_user_with_role_changes(role_changes: RoleOverride::GRANULAR_MANAGE_ASSIGNMENT_PERMISSIONS.index_with(false))
api_call(:put,
"/api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}",
{ controller: "discussion_topics", action: "update", format: "json", course_id: @course.to_param, topic_id: @topic.to_param },

View File

@ -487,94 +487,8 @@ describe "Roles API", type: :request do
end
describe "json response" do
before do
@account.root_account.disable_feature!(:granular_permissions_manage_users)
@account.root_account.disable_feature!(:granular_permissions_manage_courses)
@account.root_account.disable_feature!(:granular_permissions_manage_course_content)
@account.root_account.disable_feature!(:granular_permissions_manage_groups)
@expected_permissions = %w[
become_user
change_course_state
create_collaborations
create_conferences
manage_account_memberships
manage_account_settings
manage_admin_users
manage_alerts
manage_assignments
manage_calendar
manage_content
manage_courses
manage_files_add
manage_files_edit
manage_files_delete
manage_grades
manage_groups
manage_interaction_alerts
manage_outcomes
manage_role_overrides
manage_sections_add
manage_sections_edit
manage_sections_delete
manage_sis
manage_students
manage_user_logins
manage_wiki_create
manage_wiki_delete
manage_wiki_update
moderate_forum
post_to_forum
read_course_content
read_course_list
read_forum
read_question_banks
read_reports
read_roster
read_sis
send_messages
view_all_grades
view_group_pages
view_statistics
]
end
it "returns the expected json format with granular admin user permission off" do
json = api_call_with_settings
expect(json.keys.sort).to eq %w[
account
base_role_type
created_at
id
is_account_role
label
last_updated_at
permissions
role
workflow_state
]
expect(json["account"]["id"]).to eq @account.id
expect(json["id"]).to eq @role.id
expect(json["role"]).to eq @role_name
expect(json["base_role_type"]).to eq Role::DEFAULT_ACCOUNT_TYPE
# make sure all the expected keys are there, but don't assert on a
# *only* the expected keys, since plugins may have added more.
expect(@expected_permissions - json["permissions"].keys).to be_empty
expect(json["permissions"][@permission]).to eq({
"explicit" => false,
"readonly" => false,
"enabled" => false,
"locked" => false
})
end
it "returns the expected json format with granular admin user permission on" do
@account.root_account.enable_feature!(:granular_permissions_manage_users)
# no longer have manage_admin_users or manage_students, instead we have the new ones
expected_perms = @expected_permissions - ["manage_admin_users", "manage_students"]
expected_perms += %w[
it "returns the expected json format with granular user permissions" do
expected_perms = %w[
allow_course_admin_actions
add_teacher_to_course
add_ta_to_course
@ -618,10 +532,8 @@ describe "Roles API", type: :request do
})
end
it "returns the expected json format with granular manage courses permission on" do
@account.root_account.enable_feature!(:granular_permissions_manage_courses)
expected_perms = @expected_permissions - ["manage_courses", "change_course_state"]
expected_perms += %w[
it "returns the expected json format with granular manage courses permissions" do
expected_perms = %w[
manage_courses_add
manage_courses_admin
manage_courses_publish
@ -659,10 +571,8 @@ describe "Roles API", type: :request do
})
end
it "returns the expected json format with granular manage groups permission on" do
@account.root_account.enable_feature!(:granular_permissions_manage_groups)
expected_perms = @expected_permissions - ["manage_groups"]
expected_perms += %w[
it "returns the expected json format with granular manage groups permissions" do
expected_perms = %w[
manage_groups_add
manage_groups_manage
manage_groups_delete
@ -698,10 +608,8 @@ describe "Roles API", type: :request do
})
end
it "returns the expected json format with granular manage course content permission on" do
@account.root_account.enable_feature!(:granular_permissions_manage_course_content)
expected_perms = @expected_permissions - ["manage_content"]
expected_perms += %w[
it "returns the expected json format with granular manage course content permissions" do
expected_perms = %w[
manage_course_content_add
manage_course_content_edit
manage_course_content_delete

View File

@ -2038,7 +2038,7 @@ describe AccountsController do
# sub account admin with explicit lack of creation rights
Account.create!(name: "Strange Account", parent_account: Account.default)
Account.last.role_overrides.create! [{ role: admin_role, permission: "manage_courses_add", enabled: false }, { role: admin_role, permission: "manage_courses", enabled: false }]
Account.last.role_overrides.create! [{ role: admin_role, permission: "manage_courses_add", enabled: false }, { role: admin_role, permission: "manage_courses_admin", enabled: false }]
Account.last.account_users.create!(user: @user)
@shard2.activate do
@ -2078,7 +2078,7 @@ describe AccountsController do
it "does not mix up an admin's student creation rights with actual admin rights" do
Account.default.update(settings: { students_can_create_courses: true })
Account.default.role_overrides.create! [{ role: admin_role, permission: "manage_courses_add", enabled: false }, { role: admin_role, permission: "manage_courses", enabled: false }]
Account.default.role_overrides.create! [{ role: admin_role, permission: "manage_courses_add", enabled: false }, { role: admin_role, permission: "manage_courses_admin", enabled: false }]
Account.default.account_users.create!(user: @user)
course_with_student(user: @user, active_all: true, account: Account.default)
Account.create!(name: "Sub Account (shouldn't show up)", parent_account: Account.default)

View File

@ -221,10 +221,10 @@ RSpec.describe ApplicationController do
expect(controller.js_env[:DIRECT_SHARE_ENABLED]).to be_falsey
end
describe "with manage_content permission disabled" do
describe "with manage_course_content_add permission disabled" do
before do
course_with_teacher(active_all: true, user: @teacher)
RoleOverride.create!(context: @course.account, permission: "manage_content", role: teacher_role, enabled: false)
RoleOverride.create!(context: @course.account, permission: "manage_course_content_add", role: teacher_role, enabled: false)
end
it "sets the env var to false if the course is active" do

View File

@ -318,7 +318,7 @@ describe AssignmentsController do
it "separates manage_assignments and manage_grades permissions" do
user_session(@teacher)
@course.account.role_overrides.create! role: teacher_role, permission: "manage_assignments", enabled: false
@course.account.role_overrides.create! role: teacher_role, permission: "manage_assignments_edit", enabled: false
get "index", params: { course_id: @course.id }
expect(assigns[:js_env][:PERMISSIONS][:manage_grades]).to be_truthy
expect(assigns[:js_env][:PERMISSIONS][:manage_assignments]).to be_falsey
@ -646,9 +646,9 @@ describe AssignmentsController do
expect(assigns[:can_direct_share]).to be true
end
describe "with manage_content permission disabled" do
describe "with manage_course_content_add permission disabled" do
before do
RoleOverride.create!(context: @course.account, permission: "manage_content", role: teacher_role, enabled: false)
RoleOverride.create!(context: @course.account, permission: "manage_course_content_add", role: teacher_role, enabled: false)
end
it "does not show direct share options if the course is active" do

View File

@ -855,7 +855,7 @@ describe CoursesController do
it "sets tool creation permissions true for roles that are granted rights" do
user_session(@teacher)
get "settings", params: { course_id: @course.id }
expect(controller.js_env[:PERMISSIONS][:create_tool_manually]).to be(true)
expect(controller.js_env[:PERMISSIONS][:add_tool_manually]).to be(true)
end
it "does not set tool creation permissions for roles not granted rights" do

View File

@ -299,9 +299,9 @@ describe DiscussionTopicsController do
expect(assigns[:js_env][:DIRECT_SHARE_ENABLED]).to be(false)
end
describe "with manage_content permission disabled" do
describe "with manage_course_content_add permission disabled" do
before do
RoleOverride.create!(context: @course.account, permission: "manage_content", role: teacher_role, enabled: false)
RoleOverride.create!(context: @course.account, permission: "manage_course_content_add", role: teacher_role, enabled: false)
end
it "does not set DIRECT_SHARE_ENABLED if the course is active" do

View File

@ -1030,7 +1030,7 @@ describe MediaObjectsController do
)
user_session(@teacher)
expect(@attachment.grants_right?(@teacher, :update)).to be(false)
expect(@media_object.grants_right?(@teacher, :add_captions)).to be(true)
expect(@media_object.grants_right?(@teacher, :add_captions)).to be(false)
user_session(@teacher)
media_attachment_api_json = controller.media_attachment_api_json(@attachment, @media_object, @teacher, session)

View File

@ -258,9 +258,9 @@ describe Quizzes::QuizzesController do
expect(assigns[:js_env][:FLAGS][:DIRECT_SHARE_ENABLED]).to be(true)
end
describe "with manage_content permission disabled" do
describe "with manage_course_content_add permission disabled" do
before do
RoleOverride.create!(context: @course.account, permission: "manage_content", role: teacher_role, enabled: false)
RoleOverride.create!(context: @course.account, permission: "manage_course_content_add", role: teacher_role, enabled: false)
end
it "js_env DIRECT_SHARE_ENABLED is false if the course is active" do

View File

@ -29,8 +29,8 @@ describe SearchHelper do
load_all_contexts
expect(@contexts[:courses][@course.id][:permissions]).to be_empty
load_all_contexts(permissions: [:manage_assignments])
expect(@contexts[:courses][@course.id][:permissions][:manage_assignments]).to be_truthy
load_all_contexts(permissions: [:manage_assignments_edit])
expect(@contexts[:courses][@course.id][:permissions][:manage_assignments_edit]).to be_truthy
end
it "only loads the section and its course when given a section context" do

View File

@ -179,7 +179,7 @@ describe "External Tools" do
@member_tool.global_navigation = { url: "http://www.example.com", text: "Example URL 2" }
@member_tool.save!
@permissiony_tool = Account.default.context_external_tools.new(name: "b", domain: "google.com", consumer_key: "12345", shared_secret: "secret")
@permissiony_tool.global_navigation = { required_permissions: "manage_assignments,manage_calendar",
@permissiony_tool.global_navigation = { required_permissions: "manage_assignments_add,manage_calendar",
url: "http://www.example.com",
text: "Example URL 3" }
@permissiony_tool.save!

View File

@ -53,29 +53,31 @@ module Assignments
end
it "returns unpublished assignments if user can :manage_assignments" do
expect(@course.grants_right?(@teacher, :manage_assignments)).to be_truthy,
"precondition"
expect(@course.grants_right?(@teacher, :manage_assignments_add)).to be_truthy,
"precondition"
expect(unpublished.workflow_state).to eq("unpublished"), "precondition"
scope_filter = Assignments::ScopedToUser.new(@course, @teacher)
expect(scope_filter.scope).to include(unpublished)
end
it "does not return unpublished assignments if user cannot :manage_assignments" do
expect(@course.grants_right?(@student, :manage_assignments)).to be_falsey,
"precondition"
expect(@course.grants_right?(@student, :manage_assignments_add)).to be_falsey,
"precondition"
expect(unpublished.workflow_state).to eq("unpublished"), "precondition"
scope_filter = Assignments::ScopedToUser.new(@course, @student)
expect(scope_filter.scope).not_to include(unpublished)
end
it "returns unpublished assignments if user can :read_as_admin" do
@course.account.role_overrides.create!({
role: teacher_role,
permission: "manage_assignments",
enabled: false
})
expect(@course.grants_right?(@teacher, :manage_assignments)).to be_falsey,
"precondition"
RoleOverride::GRANULAR_MANAGE_ASSIGNMENT_PERMISSIONS.each do |permission|
@course.account.role_overrides.create!({
role: teacher_role,
permission:,
enabled: false
})
end
expect(@course.grants_right?(@teacher, :manage_assignments_add)).to be_falsey,
"precondition"
expect(@course.grants_right?(@teacher, :read_as_admin)).to be_truthy,
"precondition"
scope_filter = Assignments::ScopedToUser.new(@course, @teacher)

View File

@ -6035,19 +6035,19 @@ describe Course do
expect(@course.grants_right?(@user, :direct_share)).to be(true)
end
it "returns true for teacher with manage_content" do
it "returns true for teacher with manage_course_content_add" do
teacher_in_course(active_all: true)
expect(@course.grants_right?(@teacher, :direct_share)).to be(true)
end
it "returns false for teacher in active course without manage_content" do
RoleOverride.create!(context: @course.account, permission: "manage_content", role: teacher_role, enabled: false)
it "returns false for teacher in active course without manage_course_content_add" do
RoleOverride.create!(context: @course.account, permission: "manage_course_content_add", role: teacher_role, enabled: false)
teacher_in_course(active_all: true)
expect(@course.grants_right?(@teacher, :direct_share)).to be(false)
end
it "returns true for teacher in concluded course without manage_content" do
RoleOverride.create!(context: @course.account, permission: "manage_content", role: teacher_role, enabled: false)
it "returns true for teacher in concluded course without manage_course_content_add" do
RoleOverride.create!(context: @course.account, permission: "manage_course_content_add", role: teacher_role, enabled: false)
@course.complete!
teacher_in_course
expect(@course.grants_right?(@teacher, :direct_share)).to be(true)

View File

@ -3088,49 +3088,6 @@ describe Enrollment do
end
describe "#can_be_deleted_by" do
describe "on a student enrollment without granular_permissions_manage_users" do
let(:user) { double(id: 42) }
let(:session) { double }
before do
course_with_student
@course.root_account.disable_feature!(:granular_permissions_manage_users)
end
it "is true for a user who has been granted :manage_students" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(true)
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(false)
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_truthy
end
it "is false for a user without :manage_students" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(false)
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(false)
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_falsey
end
it "is false for someone with :manage_admin_users but without :manage_students" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(false)
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(true)
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_falsey
end
it "is false for someone with :manage_admin_users in other context" do
context = CourseSection.new(id: 10)
allow(context).to receive(:grants_right?).with(user, session, :manage_students).and_return(true)
allow(context).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(true)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_falsey
end
it "is false if a user is trying to remove their own enrollment" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(true)
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(false)
allow(@course).to receive_messages(account: @course)
@enrollment.user_id = user.id
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_falsey
end
end
describe "on a student enrollment with granular_permissions_manage_users" do
let(:user) { double(id: 42) }
let(:session) { double }
@ -3168,34 +3125,6 @@ describe Enrollment do
end
end
describe "on an observer enrollment without granular_permissions_manage_users" do
let(:user) { double(id: 42) }
let(:session) { double }
before do
course_with_observer
@course.root_account.disable_feature!(:granular_permissions_manage_users)
end
it "is true with :manage_students" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(true)
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(false)
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_truthy
end
it "is true with :manage_admin_users" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(false)
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(true)
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_truthy
end
it "is false otherwise" do
allow(@course).to receive(:grants_right?).with(user, session, :manage_students).and_return(false)
allow(@course).to receive(:grants_right?).with(user, session, :manage_admin_users).and_return(false)
expect(@enrollment.can_be_deleted_by(user, @course, session)).to be_falsey
end
end
describe "on an observer enrollment with granular_permission_manage_users" do
let(:user) { double(id: 42) }
let(:session) { double }

View File

@ -2411,7 +2411,7 @@ describe Quizzes::Quiz do
it "grants submit rights" do
allow(@course).to receive(:grants_right?).with(@student1, nil, :participate_as_student).and_return(true)
allow(@course).to receive(:grants_right?).with(@student1, nil, :manage_assignments).and_return(false)
allow(@course).to receive(:grants_right?).with(@student1, nil, :manage_assignments_edit).and_return(false)
allow(@course).to receive(:grants_right?).with(@student1, nil, :read_as_admin).and_return(false)
allow(@course).to receive(:grants_right?).with(@student1, nil, :manage_grades).and_return(false)
expect(@quiz.grants_right?(@student1, :submit)).to be true
@ -2431,7 +2431,7 @@ describe Quizzes::Quiz do
it "does not grant submit rights" do
allow(@course).to receive(:grants_right?).with(@student2, nil, :participate_as_student).and_return(true)
allow(@course).to receive(:grants_right?).with(@student2, nil, :manage_assignments).and_return(false)
allow(@course).to receive(:grants_right?).with(@student2, nil, :manage_assignments_edit).and_return(false)
allow(@course).to receive(:grants_right?).with(@student2, nil, :read_as_admin).and_return(false)
allow(@course).to receive(:grants_right?).with(@student2, nil, :manage_grades).and_return(false)
expect(@quiz.grants_right?(@student2, :submit)).to be false

View File

@ -4238,7 +4238,7 @@ describe User do
end
it "returns nil for AccountUsers without :manage_courses" do
account_admin_user_with_role_changes(user: @user, role_changes: { manage_courses: false })
account_admin_user_with_role_changes(user: @user, role_changes: { manage_courses_add: false })
expect(@user.create_courses_right(@account)).to be_nil
end

View File

@ -27,8 +27,8 @@ describe "assignment group that can't manage assignments" do
it "does not display the manage cog menu" do
@domain_root_account = Account.default
course_factory
account_admin_user_with_role_changes(role_changes: { manage_course: true,
manage_assignments: false })
account_admin_user_with_role_changes(role_changes: { manage_courses_admin: true,
manage_assignments_edit: false })
user_session(@user)
@course.require_assignment_group
@assignment_group = @course.assignment_groups.first

View File

@ -897,7 +897,7 @@ describe "people" do
expect(f("#courses")).to contain_css(".unenroll_link")
Account.default.role_overrides.create!(permission: "manage_students", enabled: false, role: admin_role)
Account.default.role_overrides.create!(permission: "remove_student_from_course", enabled: false, role: admin_role)
refresh_page
expect(f("#courses")).to_not contain_css(".unenroll_link")

View File

@ -34,7 +34,7 @@ describe "courses/_settings_sidebar" do
describe "End this course button" do
it "does not display if the course or term end date has passed" do
allow(@course).to receive(:soft_concluded?).and_return(true)
@course.update conclude_at: 1.day.ago, restrict_enrollments_to_course_dates: true
view_context(@course, @user)
assign(:current_user, @user)
render
@ -42,7 +42,7 @@ describe "courses/_settings_sidebar" do
end
it "displays if the course and its term haven't ended" do
allow(@course).to receive(:soft_concluded?).and_return(false)
@course.update conclude_at: 1.day.from_now, restrict_enrollments_to_course_dates: true
view_context(@course, @user)
assign(:current_user, @user)
render
@ -51,15 +51,6 @@ describe "courses/_settings_sidebar" do
end
describe "Reset course content" do
it "does not display the dialog contents under the button" do
@course.account.disable_feature!(:granular_permissions_manage_courses)
view_context(@course, @user)
assign(:current_user, @user)
render
doc = Nokogiri::HTML5(response.body)
expect(doc.at_css("#reset_course_content_dialog")["style"]).to eq "display:none;"
end
it "does not display the dialog contents under the button (granular permissions)" do
@course.account.enable_feature!(:granular_permissions_manage_courses)
@course.root_account.role_overrides.create!(

View File

@ -174,7 +174,7 @@ describe "shared/_select_content_dialog" do
course_with_ta account: @account, active_all: true
existing_quiz = @course.quizzes.create! title: "existing quiz"
assign(:combined_active_quizzes, [[existing_quiz.id, "existing quiz", "quiz"]])
@account.role_overrides.create! role: ta_role, permission: "manage_assignments", enabled: false
@account.role_overrides.create! role: ta_role, permission: "manage_assignments_add", enabled: false
view_context
render partial: "shared/select_content_dialog"
page = Nokogiri(response.body)