From 18bfd8b829b6e2ce5f2c926e44e291986af1bfff Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Thu, 9 Mar 2023 17:23:47 -0600 Subject: [PATCH 1/9] Added signed contract info to the user API as private data --- app/representers/api/v1/user_representer.rb | 7 ++++++ .../api/v1/user_representer_spec.rb | 22 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/representers/api/v1/user_representer.rb b/app/representers/api/v1/user_representer.rb index 9f9e53d239..e7a9059a53 100644 --- a/app/representers/api/v1/user_representer.rb +++ b/app/representers/api/v1/user_representer.rb @@ -191,5 +191,12 @@ class UserRepresenter < Roar::Decorator description: "A list of the applications the user has accessed", required: false } + + collection :signed_contract_names, + type: String, + readable: true, + writeable: false, + if: ->(user_options:, **) { user_options.try(:fetch, :include_private_data, false) }, + getter: ->(*) { FinePrint::Contract.published.latest.signed_by(self).pluck(:name) } end end diff --git a/spec/representers/api/v1/user_representer_spec.rb b/spec/representers/api/v1/user_representer_spec.rb index af2d1e60e3..f2fc6a45f5 100644 --- a/spec/representers/api/v1/user_representer_spec.rb +++ b/spec/representers/api/v1/user_representer_spec.rb @@ -2,9 +2,11 @@ RSpec.describe Api::V1::UserRepresenter, type: :representer do let(:user) { FactoryBot.create :user } - let(:school) { FactoryBot.create :school } + let(:school) { FactoryBot.create :school } subject(:representer) { described_class.new(user) } + let(:contract) { FactoryBot.create :fine_print_contract, :published } + context 'uuid' do it 'can be read' do expect(representer.to_hash['uuid']).to eq user.uuid @@ -158,4 +160,22 @@ expect { representer.from_hash(hash) }.not_to change { user.reload.grant_tutor_access } end end + + context 'signed_contract_names' do + it 'can be read' do + FactoryBot.create :fine_print_signature, contract: contract, user: user + + expect( + representer.to_hash(user_options: { include_private_data: true })['signed_contract_names'] + ).to eq [ contract.name ] + end + + it 'cannot be written (attempts are silently ignored)' do + hash = { 'signed_contract_names' => [ contract.name ] } + + expect do + representer.from_hash(hash, user_options: { include_private_data: true }) + end.not_to change { FinePrint::Signature.count } + end + end end From 21395f40b13ceff0e66cd9b54aba4d89c6310d37 Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Mon, 13 Mar 2023 09:05:28 -0500 Subject: [PATCH 2/9] Added signed_contract_names to user_matcher --- spec/support/user_hash.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/support/user_hash.rb b/spec/support/user_hash.rb index e86982c04d..cd7c4e01ea 100644 --- a/spec/support/user_hash.rb +++ b/spec/support/user_hash.rb @@ -37,6 +37,7 @@ def user_matcher(user, include_private_data: false) Api::V1::ContactInfoRepresenter.new(ci).to_hash.symbolize_keys end ) + base_hash[:signed_contract_names] = FinePrint::Contract.published.latest.signed_by(user).pluck(:name) end base_hash.delete_if { |k,v| v.nil? } From 79977aa20dd4aa6fd099448a7c1f3898ef721621 Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Mon, 13 Mar 2023 14:48:54 -0500 Subject: [PATCH 3/9] Added new route to sign latest fine print contracts by name --- app/controllers/api/v1/users_controller.rb | 16 ++++++++++++++++ app/controllers/terms_controller.rb | 6 ++++++ config/initializers/access_policies.rb | 2 +- config/routes.rb | 3 +++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index d68e4c197a..b5ae85a3e7 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -222,6 +222,22 @@ def find_or_create end end + ############################################################### + # external_id + ############################################################### + + api :POST, '/user/external_id', 'Creates an external id for a given user.' + description <<-EOS + Creates an external id for a given user. + + Both external_id and user_id must be supplied. + + Only trusted apps may call this API. All others will receive a 403 error. + + If the external_id does not exist, it is created and 201 is returned. + + #{json_schema(Api::V1::ExternalIdRepresenter, include: [:readable, :writable])} + EOS def create_external_id standard_create ExternalId.new end diff --git a/app/controllers/terms_controller.rb b/app/controllers/terms_controller.rb index b92a00f096..d51c6756c0 100644 --- a/app/controllers/terms_controller.rb +++ b/app/controllers/terms_controller.rb @@ -3,6 +3,7 @@ class TermsController < ApplicationController fine_print_skip :general_terms_of_use, :privacy_policy + before_action :allow_iframe_access, only: [:sign] before_action :get_contract, only: [:show] def index @@ -29,6 +30,11 @@ def pose @contract = FinePrint.get_contract(params[:terms].first) end + def sign + @contract = FinePrint::Contract.published.latest.find_by! params[:name] + render :pose + end + def agree handle_with(TermsAgree, complete: lambda { fine_print_return }) end diff --git a/config/initializers/access_policies.rb b/config/initializers/access_policies.rb index 24f3a82f48..bda01c1018 100644 --- a/config/initializers/access_policies.rb +++ b/config/initializers/access_policies.rb @@ -15,4 +15,4 @@ OSU::AccessPolicy.register(GroupOwner, GroupOwnerAccessPolicy) OSU::AccessPolicy.register(GroupNesting, GroupNestingAccessPolicy) OSU::AccessPolicy.register(ApplicationGroup, ApplicationGroupAccessPolicy) -OSU::AccessPolicy.register(ExternalId, ExternalIdAccessPolicy) \ No newline at end of file +OSU::AccessPolicy.register(ExternalId, ExternalIdAccessPolicy) diff --git a/config/routes.rb b/config/routes.rb index 8c2d0f9a3e..acdfd7b98f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -210,6 +210,9 @@ post 'agree', as: 'agree_to' end end + resources :terms, param: :name, only: [] do + get :sign, on: :member + end scope controller: 'static_pages' do get 'copyright' From 5746a9faaf49481aff55563761231d13c95bcce9 Mon Sep 17 00:00:00 2001 From: Dante Soares Date: Wed, 15 Mar 2023 12:27:30 -0500 Subject: [PATCH 4/9] Disable contract content sanitization and redirect back to trusted host after signing --- app/controllers/terms_controller.rb | 7 ++++++- app/views/terms/pose.html.erb | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/controllers/terms_controller.rb b/app/controllers/terms_controller.rb index d51c6756c0..cae44a5b5b 100644 --- a/app/controllers/terms_controller.rb +++ b/app/controllers/terms_controller.rb @@ -36,7 +36,12 @@ def sign end def agree - handle_with(TermsAgree, complete: lambda { fine_print_return }) + handle_with( + TermsAgree, complete: -> do + params[:r].present? && Host.trusted?(params[:r]) ? + redirect_to(params[:r]) : fine_print_return + end + ) end protected diff --git a/app/views/terms/pose.html.erb b/app/views/terms/pose.html.erb index 219a7a38e4..eaf268e23b 100644 --- a/app/views/terms/pose.html.erb +++ b/app/views/terms/pose.html.erb @@ -7,10 +7,10 @@ <% end %>
- <%= simple_format(@contract.content) %> + <%= simple_format @contract.content, {}, sanitize: false %>
- <%= lev_form_for :agreement, url: agree_to_terms_path, method: :post do |f| %> + <%= lev_form_for :agreement, url: agree_to_terms_path(r: params[:r]), method: :post do |f| %>
- <%= lev_form_for :agreement, url: agree_to_terms_path(r: params[:r]), method: :post do |f| %> + <%= lev_form_for( + :agreement, + url: agree_to_terms_path(r: params[:r], token: params[:token]), + method: :post + ) do |f| %>