set secondary email addresses and store taken ones

This commit is contained in:
Leo McArdle 2018-08-24 15:23:45 +01:00
Родитель ecc1b18a23
Коммит 46fa806fa1
13 изменённых файлов: 329 добавлений и 10 удалений

Просмотреть файл

@ -23,4 +23,24 @@
</div>
</div>
<div class='display-row secondary-emails'>
<div class='field'>Taken Emails</div>
<div class='value'>
{{#if model.email}}
{{#if model.mozilla_iam.taken_emails}}
<ul>
{{#each model.mozilla_iam.taken_emails as |email| }}
<li><a href="mailto:{{unbound email}}">{{email}}</a></li>
{{/each}}
</ul>
{{else}}
&mdash;
{{/if}}
{{else}}
Click " {{d-icon "envelope-o"}} Show " above
{{/if}}
</div>
</div>
</section>

Просмотреть файл

@ -5,7 +5,9 @@ module MozillaIAM
object.custom_fields.select do |k, v|
k.start_with?('mozilla_iam')
end.map do |k, v|
[k.sub('mozilla_iam_', ''), v]
key = k.sub('mozilla_iam_', '')
val = Array(v) if Profile.array_keys.include?(key.to_sym)
[key, val || v]
end.to_h
end

Просмотреть файл

@ -16,10 +16,12 @@ module MozillaIAM
class Profile
attr_reader :groups
attr_reader :secondary_emails
def initialize(raw)
@raw = raw
@groups = Array(raw[:groups]) | Array(raw.dig(:app_metadata, :groups))
@secondary_emails = Array(raw[:email_aliases])
end
end
end

Просмотреть файл

@ -11,10 +11,29 @@ module MozillaIAM
end
def profile(uid)
profile = get("profile/#{uid}")
MultiJson.load(profile[:body], symbolize_keys: true)
res = get("profile/#{uid}")
Profile.new(MultiJson.load(res[:body], symbolize_keys: true))
end
class Profile
attr_reader :secondary_emails
def initialize(raw)
@raw = raw
@secondary_emails = process_emails
end
private
def process_emails
emails = @raw[:emails]
if emails
emails.select { |x| x[:verified] && !x[:primary] }.map { |x| x[:value] }.uniq
else
[]
end
end
end
end
end
end

Просмотреть файл

@ -1,14 +1,20 @@
module MozillaIAM
class Profile
@refresh_methods = []
@array_keys = []
class << self
attr_accessor :refresh_methods
attr_accessor :array_keys
def during_refresh(method_name)
refresh_methods << method_name
end
def register_as_array(key)
array_keys << key
end
def refresh(user)
uid = get(user, :uid)
return if uid.blank?
@ -43,6 +49,8 @@ module MozillaIAM
value = @api_profiles[api.name].send(attr)
if response.nil?
response = value
elsif [response, value].map { |x| x.kind_of? Array }.all?
response = response | value
end
end
return response
@ -87,3 +95,4 @@ module MozillaIAM
end
require_relative "profile/update_groups"
require_relative "profile/update_emails"

Просмотреть файл

@ -0,0 +1,27 @@
module MozillaIAM
Profile.class_eval do
during_refresh :update_emails
register_as_array :taken_emails
private
def store_taken_email_or_raise(e, taken_emails)
raise e unless e.message == "Validation failed: Email has already been taken"
taken_emails << e.record.email
end
def update_emails
emails = attr(:secondary_emails)
taken_emails = []
@user.user_emails.where(primary: false).where.not(email: emails).delete_all
emails.each do |email|
begin
UserEmail.find_or_create_by!(user: @user, email: email)
rescue ActiveRecord::RecordInvalid => e
store_taken_email_or_raise(e, taken_emails)
end
end
set(:taken_emails, taken_emails)
end
end
end

Просмотреть файл

@ -69,5 +69,22 @@ describe MozillaIAM::API::Management do
include_examples "empty array"
end
end
describe "#secondary_emails" do
it "returns content of email_aliases" do
profile = described_class.new({ email_aliases: ["one", "two"] })
expect(profile.secondary_emails).to contain_exactly "one", "two"
end
it "is empty array when email_alises is empty" do
profile = described_class.new({ email_aliases: [] })
expect(profile.secondary_emails).to eq []
end
it "is empty array email_alises is nil" do
profile = described_class.new({ })
expect(profile.secondary_emails).to eq []
end
end
end
end

Просмотреть файл

@ -17,12 +17,79 @@ describe MozillaIAM::API::Person do
context "#profile" do
it "returns the profile for a specific user" do
api.expects(:get).with("profile/uid").returns(body: '{"profile":"profile"}')
expect(api.profile("uid")[:profile]).to eq "profile"
expect(api.profile("uid").instance_variable_get(:@raw)).to eq({profile: "profile"})
end
it "returns an empty hash if a profile doesn't exist" do
api.expects(:get).with("profile/uid").returns(body: '{}')
expect(api.profile("uid")).to eq({})
expect(api.profile("uid").instance_variable_get(:@raw)).to eq({})
end
end
describe described_class::Profile do
describe "#secondary_emails" do
context "with no emails attribute in profile" do
let(:profile) { described_class.new({}) }
it "returns empty array" do
expect(profile.secondary_emails).to eq []
end
end
context "with empy emails attribute in profile" do
let(:profile) { described_class.new({ emails: [] }) }
it "returns empty array" do
expect(profile.secondary_emails).to eq []
end
end
context "with primary email in profile" do
let(:profile) { described_class.new({ emails: [
{ verified: true, value: "first@example.com", primary: true }
] }) }
it "returns empty array" do
expect(profile.secondary_emails).to eq []
end
end
context "with multiple primary emails in profile" do
let(:profile) { described_class.new({ emails: [
{ verified: true, value: "first@example.com", primary: true },
{ verified: true, value: "second@example.com", primary: true }
] }) }
it "returns empty array" do
expect(profile.secondary_emails).to eq []
end
end
context "secondary emails in profile" do
let(:profile) { described_class.new({ emails: [
{ verified: true, value: "first@example.com", primary: true },
{ verified: true, value: "second@example.com", primary: false },
{ verified: true, value: "third@example.com", primary: false },
] }) }
it "returns secondary emails" do
expect(profile.secondary_emails).to contain_exactly("second@example.com", "third@example.com")
end
end
context "with unverified emails in profile" do
let(:profile) { described_class.new({ emails: [
{ verified: false, value: "first_unverified@example.com", primary: true },
{ verified: true, value: "first@example.com", primary: true },
{ verified: false, value: "second_unverified@example.com", primary: true },
{ verified: true, value: "second@example.com", primary: false },
{ verified: true, value: "third@example.com", primary: false },
] }) }
it "returns verified secondary emails" do
expect(profile.secondary_emails).to contain_exactly("second@example.com", "third@example.com")
end
end
end
end
end

Просмотреть файл

@ -0,0 +1,112 @@
require_relative '../../../iam_helper'
describe MozillaIAM::Profile do
describe described_class.refresh_methods do
it { should include(:update_emails) }
end
let(:email) { "one@email.com" }
let(:secondary_emails) { ["two@email.com", "three@email.com"] }
let(:user) { user = Fabricate(:user_single_email, email: email) }
let(:profile) { MozillaIAM::Profile.new(user, "uid") }
def mock_profile_emails(*secondary)
profile.stubs(:attr).with(:secondary_emails).returns(secondary)
end
before do
secondary_emails.each do |email|
Fabricate(:secondary_email, user: user, email: email)
end
mock_profile_emails(*secondary_emails)
end
describe "#store_taken_email_or_raise" do
let(:taken_emails) { [] }
before do
u = Fabricate(:user, email: "taken_primary@email.com")
Fabricate(:secondary_email, user: u, email: "taken_secondary@email.com")
end
it "stores email if secondary email is taken as a primary email" do
begin
Fabricate(:secondary_email, user: user, email: "taken_primary@email.com")
rescue Exception => e
profile.send(:store_taken_email_or_raise, e, taken_emails)
expect(taken_emails).to contain_exactly("taken_primary@email.com")
end
end
it "stores email if secondary email is taken as secondary email" do
begin
Fabricate(:secondary_email, user: user, email: "taken_secondary@email.com")
rescue Exception => e
profile.send(:store_taken_email_or_raise, e, taken_emails)
expect(taken_emails).to contain_exactly("taken_secondary@email.com")
end
end
it "raises exception if it's not because of a taken email" do
begin
Fabricate(:user_email, user: user, email: "second_primary@email.com")
rescue Exception => e
expect { profile.send(:store_taken_email_or_raise, e, taken_emails) }.to raise_exception e
expect(taken_emails).to eq []
end
end
end
describe "#update_emails" do
shared_examples "leaves primary" do
it "leaves primary email alone" do
profile.send(:update_emails)
expect(user.email).to eq email
end
end
include_examples "leaves primary"
it "leaves secondary emails alone" do
profile.send(:update_emails)
expect(user.secondary_emails).to match_array secondary_emails
end
context "when profile has no secondary emails" do
before { mock_profile_emails() }
include_examples "leaves primary"
it "removes seconary emails" do
profile.send(:update_emails)
expect(user.secondary_emails).to eq []
end
end
context "when profile has different secondary emails" do
before { mock_profile_emails("two@email.com", "four@email.com") }
include_examples "leaves primary"
it "updates secondary emails" do
profile.send(:update_emails)
expect(user.secondary_emails).to contain_exactly("two@email.com", "four@email.com")
end
context "and some of those emails are taken" do
before do
u = Fabricate(:user, email: "taken1@email.com")
Fabricate(:secondary_email, user: u, email: "taken2@email.com")
mock_profile_emails("taken1@email.com", "two@email.com", "taken2@email.com", "four@email.com")
end
include_examples "leaves primary"
it "stores taken emails, and updates the rest" do
profile.send(:update_emails)
expect(profile.send(:get, :taken_emails)).to contain_exactly("taken1@email.com", "taken2@email.com")
expect(user.secondary_emails).to contain_exactly("two@email.com", "four@email.com")
end
end
end
end
end

Просмотреть файл

@ -19,6 +19,21 @@ describe MozillaIAM::Profile do
end
end
describe ".register_as_array" do
it "adds key to .array_keys" do
described_class.register_as_array :foo
expect(described_class.array_keys).to include :foo
described_class.register_as_array :bar
expect(described_class.array_keys).to include :bar
described_class.array_keys.delete(:foo)
described_class.array_keys.delete(:bar)
expect(described_class.array_keys).not_to include :foo
expect(described_class.array_keys).not_to include :bar
end
end
context '.refresh' do
it "refreshes a user who already has a profile" do
profile
@ -101,6 +116,7 @@ describe MozillaIAM::Profile do
def initialize(rand); @rand = rand; end
def foo; :foo; end
def foobar; :foo; end
def array; [1, 2, 3]; end
end
end
class Bar
@ -108,6 +124,7 @@ describe MozillaIAM::Profile do
class Profile
def bar; :bar; end
def foobar; :bar; end
def array; [3, 4, 5]; end
end
end
MozillaIAM::API.stubs(:profile_apis).returns([Foo, Bar])
@ -132,6 +149,10 @@ describe MozillaIAM::Profile do
MozillaIAM::API.stubs(:profile_apis).returns([Bar, Foo])
expect(profile.attr(:foobar)).to eq :bar
end
it "takes the union of the attribute if it's an array" do
expect(profile.attr(:array)).to contain_exactly(1, 2, 3, 4, 5)
end
end
it "caches Profile instances" do

Просмотреть файл

@ -28,7 +28,7 @@ describe MozillaIAM do
end
context 'when user in correct IAM group' do
before { stub_management_api_profile_request(uid, groups: ['iam_group']) }
before { stub_apis_profile_request(uid, groups: ['iam_group']) }
it 'refreshes the user profile' do
PostAlerter.post_created(reply)
@ -55,7 +55,7 @@ describe MozillaIAM do
end
context 'when user removed from IAM group' do
before { stub_management_api_profile_request(uid, groups: []) }
before { stub_apis_profile_request(uid, groups: []) }
it 'refreshes the user profile' do
PostAlerter.post_created(reply)

Просмотреть файл

@ -25,5 +25,14 @@ describe AdminDetailedUserSerializer do
expect(json[:mozilla_iam]).to be_empty
end
it "should return registered custom fields as arrays" do
MozillaIAM::Profile.stubs(:array_keys).returns([:array])
user.custom_fields['mozilla_iam_array'] = "element"
mozilla_iam = json[:mozilla_iam]
expect(mozilla_iam['array']).to eq ["element"]
end
end
end

Просмотреть файл

@ -72,6 +72,7 @@ module IAMHelpers
end
def authenticate_user(user)
MozillaIAM::Profile.stubs(:refresh_methods).returns([])
authenticate_with_id_token create_id_token(user)
end
@ -90,17 +91,25 @@ module IAMHelpers
kid: 'the_best_key'
}
req_body = {
audience: aud,
client_id: "the_best_client_id",
client_secret: "",
grant_type: "client_credentials"
}
access_token = JWT.encode(payload, private_key, 'RS256', header_fields)
body = MultiJson.dump(access_token: access_token)
res_body = MultiJson.dump(access_token: access_token)
stub_request(:post, 'https://auth.mozilla.auth0.com/oauth/token')
.to_return(status: 200, body: body)
.with(body: req_body)
.to_return(status: 200, body: res_body)
end
def stub_people_api_profile_request(uid, profile)
stub_oauth_token_request('https://person-api.sso.mozilla.com')
stub_request(:get, "https://uhbz4h3wa8.execute-api.us-west-2.amazonaws.com/prod/profile/#{uid}")
stub_request(:get, "https://person-api.sso.mozilla.com/v1/profile/#{uid}")
.to_return(status: 200, body: MultiJson.dump(body: MultiJson.dump(profile)))
end
@ -117,4 +126,9 @@ 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)
end
end