diff --git a/lib/mozilla_iam/application_extensions.rb b/lib/mozilla_iam/application_extensions.rb index 21e3f72..c10dcfc 100644 --- a/lib/mozilla_iam/application_extensions.rb +++ b/lib/mozilla_iam/application_extensions.rb @@ -31,6 +31,10 @@ module MozillaIAM log_off_user else refresh_iam_session + unless Profile.for(current_user).is_aal_enough?(session[:mozilla_iam].try(:[], :aal)) + reset_session + log_off_user + end end rescue => e reset_session diff --git a/lib/mozilla_iam/authenticator.rb b/lib/mozilla_iam/authenticator.rb index b614864..7fbf89c 100644 --- a/lib/mozilla_iam/authenticator.rb +++ b/lib/mozilla_iam/authenticator.rb @@ -18,8 +18,10 @@ module MozillaIAM ::PluginStore.set('mozilla-iam', 'logout_delay', logout_delay) Rails.cache.write('mozilla-iam/logout_delay', logout_delay) + aal = payload['https://sso.mozilla.com/claim/AAL'] auth_token[:session][:mozilla_iam] = { - last_refresh: Time.now + last_refresh: Time.now, + aal: aal } result = Auth::Result.new @@ -35,7 +37,11 @@ module MozillaIAM result.extra_data = { uid: uid } if user - Profile.new(user, uid).force_refresh + profile = Profile.new(user, uid) + profile.force_refresh + unless profile.is_aal_enough?(aal) + raise "user logged in with too low an AAL" + end end result diff --git a/lib/mozilla_iam/profile.rb b/lib/mozilla_iam/profile.rb index c62ac57..2fd121b 100644 --- a/lib/mozilla_iam/profile.rb +++ b/lib/mozilla_iam/profile.rb @@ -102,3 +102,4 @@ end require_relative "profile/update_groups" require_relative "profile/update_emails" require_relative "profile/duplicate_accounts" +require_relative "profile/is_aal_enough" diff --git a/lib/mozilla_iam/profile/is_aal_enough.rb b/lib/mozilla_iam/profile/is_aal_enough.rb new file mode 100644 index 0000000..cd79bb1 --- /dev/null +++ b/lib/mozilla_iam/profile/is_aal_enough.rb @@ -0,0 +1,25 @@ +module MozillaIAM + Profile.class_eval do + def is_aal_enough?(aal) + aal_levels = [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "MAXIMUM" + ] + level = aal_levels.index(aal) + level = aal_levels.index("UNKNOWN") if !level + + if get(:in_mapped_groups) == "t" + level >= aal_levels.index("MEDIUM") + elsif @user.moderator + level >= aal_levels.index("MEDIUM") + elsif @user.admin + level >= aal_levels.index("MEDIUM") + else + true + end + end + end +end diff --git a/lib/mozilla_iam/profile/update_groups.rb b/lib/mozilla_iam/profile/update_groups.rb index be4c552..4206c38 100644 --- a/lib/mozilla_iam/profile/update_groups.rb +++ b/lib/mozilla_iam/profile/update_groups.rb @@ -5,13 +5,16 @@ module MozillaIAM private def update_groups + in_mapped_groups = false GroupMapping.all.each do |mapping| if attr(:groups).include?(mapping.iam_group_name) + in_mapped_groups = true add_to_group(mapping.group) else remove_from_group(mapping.group) end end + set(:in_mapped_groups, in_mapped_groups) end def add_to_group(group) diff --git a/plugin.rb b/plugin.rb index e54ff47..a830ca0 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,6 +1,6 @@ # name: mozilla-iam # about: A plugin to integrate Discourse with Mozilla's Identity and Access Management (IAM) system -# version: 0.2.11 +# version: 0.2.11-AAL-0 # authors: Leo McArdle # url: https://github.com/mozilla/discourse-mozilla-iam diff --git a/spec/components/mozilla_iam/authenticator_spec.rb b/spec/components/mozilla_iam/authenticator_spec.rb index 61c88b3..48fc405 100644 --- a/spec/components/mozilla_iam/authenticator_spec.rb +++ b/spec/components/mozilla_iam/authenticator_spec.rb @@ -125,6 +125,33 @@ describe MozillaIAM::Authenticator do expect(result.failed).to eq true end + + context "when the AAL" do + let(:user) { Fabricate(:user) } + let(:id_token) { create_id_token(user, { "https://sso.mozilla.com/claim/AAL" => "LOW" }) } + before do + MozillaIAM::Profile.expects(:refresh_methods).returns([:update_groups]) + MozillaIAM::GroupMapping.create(iam_group_name: 'iam_group', group: Fabricate(:group)) + end + + context "is high enough" do + it "authenticates user" do + MozillaIAM::Profile.any_instance.expects(:attr).with(:groups).returns([]) + result = authenticate_with_id_token(id_token) + + expect(result.user.id).to eq user.id + end + end + + context "is too low" do + it "doesn't authenticate user" do + MozillaIAM::Profile.any_instance.expects(:attr).with(:groups).returns(["iam_group"]) + result = authenticate_with_id_token(id_token) + + expect(result.failed).to eq true + end + end + end end context '#after_create_account' do diff --git a/spec/components/mozilla_iam/profile/is_aal_enough_spec.rb b/spec/components/mozilla_iam/profile/is_aal_enough_spec.rb new file mode 100644 index 0000000..6cae50d --- /dev/null +++ b/spec/components/mozilla_iam/profile/is_aal_enough_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../../iam_helper' + +describe MozillaIAM::Profile do + describe "#is_aal_enough?" do + let(:user) { Fabricate(:user) } + let(:profile) { MozillaIAM::Profile.new(user, "uid") } + + context "when a user is in no mapped groups" do + before do + profile.expects(:get).with(:in_mapped_groups).returns("f") + end + + it "returns true with nil" do + expect(profile.is_aal_enough?(nil)).to eq true + end + + it "returns true with UNKNOWN" do + expect(profile.is_aal_enough?("UNKNOWN")).to eq true + end + + it "returns true with LOW" do + expect(profile.is_aal_enough?("LOW")).to eq true + end + + it "returns true with MEDIUM" do + expect(profile.is_aal_enough?("MEDIUM")).to eq true + end + + it "returns true with HIGH" do + expect(profile.is_aal_enough?("HIGH")).to eq true + end + + it "returns true with MAXIMUM" do + expect(profile.is_aal_enough?("MAXIMUM")).to eq true + end + end + + shared_examples "MEDIUM required" do + it "returns false with nil" do + expect(profile.is_aal_enough?(nil)).to eq false + end + + it "returns false with UNKNOWN" do + expect(profile.is_aal_enough?("UNKNOWN")).to eq false + end + + it "returns false with LOW" do + expect(profile.is_aal_enough?("LOW")).to eq false + end + + it "returns true with MEDIUM" do + expect(profile.is_aal_enough?("MEDIUM")).to eq true + end + + it "returns true with HIGH" do + expect(profile.is_aal_enough?("HIGH")).to eq true + end + + it "returns true with MAXIMUM" do + expect(profile.is_aal_enough?("MAXIMUM")).to eq true + end + end + + context "when a user is in mapped groups" do + before do + profile.expects(:get).with(:in_mapped_groups).returns("t") + end + + include_examples "MEDIUM required" + end + + context "when a user is a moderator" do + let(:user) { Fabricate(:moderator) } + + include_examples "MEDIUM required" + end + + context "when a user is an admin" do + let(:user) { Fabricate(:admin) } + + include_examples "MEDIUM required" + end + + end +end diff --git a/spec/components/mozilla_iam/profile/update_groups_spec.rb b/spec/components/mozilla_iam/profile/update_groups_spec.rb index 9bbd0d8..bf88b29 100644 --- a/spec/components/mozilla_iam/profile/update_groups_spec.rb +++ b/spec/components/mozilla_iam/profile/update_groups_spec.rb @@ -35,6 +35,22 @@ describe MozillaIAM::Profile do expect(group.users.count).to eq 1 end + + context "when a user is in a mapped group" do + it "sets in_mapped_groups to true" do + profile.expects(:attr).with(:groups).returns(['iam_group']) + profile.send(:update_groups) + expect(profile.send(:get, :in_mapped_groups)).to eq "t" + end + end + + context "when a user isn't in any mapped groups" do + it "sets in_mapped_groups to false" do + profile.expects(:attr).with(:groups).returns([]) + profile.send(:update_groups) + expect(profile.send(:get, :in_mapped_groups)).to eq "f" + end + end end context "#add_to_group" do diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 62f3752..3069607 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -99,6 +99,20 @@ describe TopicsController do expect(session[:mozilla_iam][:no_refresh]).to eq true end end + + context "and when MEDIUM or above AAL required" do + it "kills session" do + MozillaIAM::Profile.any_instance.expects(:is_aal_enough?).with(nil).returns(true) + + get :show, params: { id: 666 }, format: :json + expect(session['current_user_id']).to be + + MozillaIAM::Profile.any_instance.expects(:is_aal_enough?).with(nil).returns(false) + + get :show, params: { id: 666 }, format: :json + expect(session['current_user_id']).to be_nil + end + end end context "with session[:mozilla_iam][:no_refresh] set to true" do @@ -134,5 +148,24 @@ describe TopicsController do end end end + + context "when the AAL becomes too low" do + it "kills session" do + user = Fabricate(:user) + authenticate_user(user) + log_in_user(user) + session[:mozilla_iam] = { aal: "LOW" } + + MozillaIAM::Profile.any_instance.expects(:is_aal_enough?).with("LOW").returns(true) + + get :show, params: { id: 666 }, format: :json + expect(session['current_user_id']).to be + + MozillaIAM::Profile.any_instance.expects(:is_aal_enough?).with("LOW").returns(false) + + get :show, params: { id: 666 }, format: :json + expect(session['current_user_id']).to be_nil + end + end end end diff --git a/spec/support/iam_helpers.rb b/spec/support/iam_helpers.rb index 2391c5c..d3d4d5a 100644 --- a/spec/support/iam_helpers.rb +++ b/spec/support/iam_helpers.rb @@ -45,7 +45,8 @@ module IAMHelpers iss: 'https://auth.mozilla.auth0.com/', aud: 'the_best_client_id', exp: Time.now.to_i + 7.days, - iat: Time.now.to_i + iat: Time.now.to_i, + "https://sso.mozilla.com/claim/AAL": "UNKNOWN" }.merge(additional_payload) header_fields = { @@ -126,7 +127,7 @@ module IAMHelpers expect { parent.const_get(const) }.to raise_error(NameError) end end - + def stub_apis_profile_request(uid, profile) stub_management_api_profile_request(uid, profile) stub_people_api_profile_request(uid, profile)